@blotout/qinto-cli 0.1.21 → 0.1.22

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.
@@ -4,6 +4,10 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // ../../../libs/app-manifest/src/app-roles.ts
8
+ var DECLARABLE_APP_ROLES = ["owner", "admin", "manager", "editor", "member", "viewer"];
9
+ var APP_ROLES = ["full", ...DECLARABLE_APP_ROLES];
10
+
7
11
  // ../../../libs/app-manifest/src/cron.ts
8
12
  var MAX_CRON_LENGTH = 128;
9
13
  var MINUTE_LITERAL = /^([0-9]|[1-5][0-9])$/;
@@ -18947,6 +18951,10 @@ var VARIABLE_NAME = /^[A-Z][A-Z0-9_]*$/;
18947
18951
  var APP_SLUG_PATTERN = /^[a-z][a-z0-9-]{2,29}$/;
18948
18952
  var FUNCTION_NAME = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
18949
18953
  var identifierField = external_exports.string().min(1).max(64).regex(IDENTIFIER);
18954
+ var RENDERABLE_TEXT = /^[^\u0000-\u0009\u000B-\u001F\u007F\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]*$/;
18955
+ var renderableText = (min, max) => {
18956
+ return external_exports.string().min(min).max(max).regex(RENDERABLE_TEXT, "must not carry control or bidirectional-override characters");
18957
+ };
18950
18958
  var httpsUrl = (field2) => {
18951
18959
  return external_exports.url().max(2048).refine((value) => value.startsWith("https://"), `${field2} must be an HTTPS URL`);
18952
18960
  };
@@ -19082,6 +19090,35 @@ var validateOauth = (input2, ctx) => {
19082
19090
  }
19083
19091
  };
19084
19092
 
19093
+ // ../../../libs/app-manifest/src/roles.ts
19094
+ var MAX_APP_ROLE_DESCRIPTION = 200;
19095
+ var roleSchema = external_exports.object({
19096
+ key: external_exports.enum(DECLARABLE_APP_ROLES, {
19097
+ error: (issue2) => {
19098
+ return issue2.input === "full" ? "full access is always available and is not declared" : void 0;
19099
+ }
19100
+ }),
19101
+ description: renderableText(1, MAX_APP_ROLE_DESCRIPTION).optional()
19102
+ }).strict();
19103
+ var rolesSchema = external_exports.array(roleSchema).max(DECLARABLE_APP_ROLES.length).superRefine((roles, ctx) => {
19104
+ uniqueOrIssue(
19105
+ ctx,
19106
+ roles.map((role) => role.key),
19107
+ [],
19108
+ "role"
19109
+ );
19110
+ }).transform((roles) => {
19111
+ return [...roles].sort((a, b) => APP_ROLES.indexOf(a.key) - APP_ROLES.indexOf(b.key));
19112
+ });
19113
+ var validateRoles = (manifest, ctx) => {
19114
+ const declared = /* @__PURE__ */ new Set(["full", ...(manifest.roles ?? []).map((role) => role.key)]);
19115
+ for (const [index, tool] of manifest.tools.entries()) {
19116
+ if (tool.role !== void 0 && !declared.has(tool.role)) {
19117
+ ctx.addIssue({ code: "custom", path: ["tools", index, "role"], message: `Tool role ${tool.role} is not declared in roles` });
19118
+ }
19119
+ }
19120
+ };
19121
+
19085
19122
  // ../../../libs/app-manifest/src/route.ts
19086
19123
  var ENDPOINT_PATH_PATTERN = /^\/(?:(?::[a-zA-Z][a-zA-Z0-9_]*|[a-zA-Z0-9_-]+)(?:\/(?::[a-zA-Z][a-zA-Z0-9_]*|[a-zA-Z0-9_-]+))*)?$/;
19087
19124
  var isParam = (segment) => {
@@ -19115,10 +19152,6 @@ var MAX_MANIFEST_TOOLS_BYTES = 150 * 1024;
19115
19152
  var TOOL_PROPERTY_NAME = /^[a-zA-Z][a-zA-Z0-9_]*$/;
19116
19153
  var RESERVED_TOOL_ARGUMENTS = ["teamId", "siteId", "installId"];
19117
19154
  var TOOL_STRING_FORMATS = ["uuid", "date", "date-time", "email", "uri"];
19118
- var RENDERABLE_TEXT = /^[^\u0000-\u0009\u000B-\u001F\u007F\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]*$/;
19119
- var renderableText = (min, max) => {
19120
- return external_exports.string().min(min).max(max).regex(RENDERABLE_TEXT, "must not carry control or bidirectional-override characters");
19121
- };
19122
19155
  var propertyName = external_exports.string().min(1).max(48).regex(TOOL_PROPERTY_NAME);
19123
19156
  var FORMAT_SCHEMAS = {
19124
19157
  uuid: external_exports.uuid(),
@@ -19214,7 +19247,8 @@ var toolSchema = external_exports.object({
19214
19247
  title: renderableText(1, MAX_APP_TOOL_TITLE_LENGTH),
19215
19248
  description: renderableText(MIN_APP_TOOL_DESCRIPTION, MAX_APP_TOOL_DESCRIPTION),
19216
19249
  inputSchema: toolInputSchema,
19217
- annotations: toolAnnotationsSchema
19250
+ annotations: toolAnnotationsSchema,
19251
+ role: external_exports.enum(APP_ROLES).optional()
19218
19252
  }).strict();
19219
19253
  var nodeChildren = (value) => {
19220
19254
  const properties = value["properties"];
@@ -19618,6 +19652,9 @@ var baseSchema = external_exports.object({
19618
19652
  description: "The push topics this app broadcasts to every app on the site. Requires the `sharedContext` data capability; a topic left out reaches only this app\u2019s own browser code."
19619
19653
  }),
19620
19654
  endpoints: external_exports.array(endpointSchema).max(16).default([]).meta({ description: "The HTTP routes served at https://<site>/app/<slug><path>." }),
19655
+ roles: rolesSchema.optional().meta({
19656
+ description: "The app roles this app uses, from the fixed list owner, admin, manager, editor, member, viewer. Full access is always available and is not declared. A team member is given one app role per app."
19657
+ }),
19621
19658
  tools: toolsSchema.meta({
19622
19659
  description: "The MCP tools this app serves to AI assistants, published as `<slug>_<name>`. Each one a site manager accepts becomes callable on their behalf by an assistant."
19623
19660
  }),
@@ -19974,6 +20011,7 @@ var appManifestSchema = baseSchema.superRefine((manifest, ctx) => {
19974
20011
  validateOauth(manifest, ctx);
19975
20012
  validateServer(manifest, ctx);
19976
20013
  validateTools(manifest.tools, ctx);
20014
+ validateRoles(manifest, ctx);
19977
20015
  });
19978
20016
 
19979
20017
  // ../../../libs/app-manifest/src/oauth-flow.ts
@@ -20188,7 +20226,12 @@ var toolSchema2 = external_exports.object({
20188
20226
  name: external_exports.string().min(1).max(MAX_APP_TOOL_NAME_LENGTH).regex(APP_TOOL_NAME_PATTERN),
20189
20227
  // Who asked, and what they may do on this site. Derived from the bearer by the portal, never
20190
20228
  // from the model's arguments.
20191
- viewer: external_exports.object({ userId: external_exports.string().min(1).max(64), access: external_exports.enum(["read", "write"]) }).strict(),
20229
+ viewer: external_exports.object({
20230
+ userId: external_exports.string().min(1).max(64),
20231
+ access: external_exports.enum(["read", "write"]),
20232
+ // Optional only while a site worker or app runtime that predates it can still receive this envelope; absent means `full`.
20233
+ appRole: external_exports.enum(APP_ROLES).optional()
20234
+ }).strict(),
20192
20235
  // Already validated against the tool's declared inputSchema, with the reserved routing names
20193
20236
  // stripped. Every declared input schema has an object root, so this is an object too — and a
20194
20237
  // call that names no arguments normalizes to `{}` rather than reaching the handler as undefined.
@@ -20416,7 +20459,12 @@ var appUiAnalyticsQueryPath = (domainId, appSlug2, resourceIdentifier) => {
20416
20459
  var uiInstallSchema = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100), enabled: external_exports.boolean() }).strict();
20417
20460
  var uiContextSchema = external_exports.object({
20418
20461
  version: external_exports.string().min(1).max(32),
20419
- viewer: external_exports.object({ userId: external_exports.string().min(1).max(64), access: external_exports.enum(["read", "write"]) }).strict(),
20462
+ viewer: external_exports.object({
20463
+ userId: external_exports.string().min(1).max(64),
20464
+ access: external_exports.enum(["read", "write"]),
20465
+ // Optional only while a site worker that predates it can still send this header; absent means `full`.
20466
+ appRole: external_exports.enum(APP_ROLES).optional()
20467
+ }).strict(),
20420
20468
  install: uiInstallSchema,
20421
20469
  installs: external_exports.array(uiInstallSchema).min(1).max(MAX_INSTALLS_PER_APP),
20422
20470
  writeTicket: external_exports.string().min(1).max(2048).optional()
@@ -21941,7 +21989,8 @@ var buildContext2 = (envelope, env) => {
21941
21989
  env,
21942
21990
  site,
21943
21991
  install: { id: install.id, label: install.label },
21944
- viewer: envelope.tool.viewer,
21992
+ // An envelope from a portal that predates the role, or one carrying `full`, has no `appRole`.
21993
+ viewer: { ...envelope.tool.viewer, appRole: envelope.tool.viewer.appRole ?? "full" },
21945
21994
  callId: envelope.tool.callId,
21946
21995
  startWorkflow: buildStartWorkflow({ ord: install.ord, installId: install.id, installLabel: install.label, anonId: null }),
21947
21996
  oauth: buildOauth(env, install.ord),
@@ -22624,7 +22673,8 @@ var dispatchUi = async ({ request, env, versions }) => {
22624
22673
  secrets,
22625
22674
  appVariables,
22626
22675
  appSecrets,
22627
- viewer: context.viewer,
22676
+ // A site worker that predates app roles sends none, and every member had full access then.
22677
+ viewer: { ...context.viewer, appRole: context.viewer.appRole ?? "full" },
22628
22678
  observability: buildObservability(context.install.ord),
22629
22679
  identity: buildIdentity(env),
22630
22680
  apps: buildAppsClient(env, { depth: 0 })
@@ -790,6 +790,10 @@ var deriveVisitorSignature = /* @__PURE__ */ __name(async (masterBase64, domainI
790
790
  return hmacHex(masterBase64, `${domainId}#visitor#${anonId}`);
791
791
  }, "deriveVisitorSignature");
792
792
 
793
+ // ../../libs/app-manifest/src/app-roles.ts
794
+ var DECLARABLE_APP_ROLES = ["owner", "admin", "manager", "editor", "member", "viewer"];
795
+ var APP_ROLES = ["full", ...DECLARABLE_APP_ROLES];
796
+
793
797
  // ../../libs/app-manifest/src/cron.ts
794
798
  var MAX_CRON_LENGTH = 128;
795
799
  var MINUTE_LITERAL = /^([0-9]|[1-5][0-9])$/;
@@ -20348,6 +20352,10 @@ var VARIABLE_NAME = /^[A-Z][A-Z0-9_]*$/;
20348
20352
  var APP_SLUG_PATTERN = /^[a-z][a-z0-9-]{2,29}$/;
20349
20353
  var FUNCTION_NAME = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
20350
20354
  var identifierField = external_exports.string().min(1).max(64).regex(IDENTIFIER);
20355
+ var RENDERABLE_TEXT = /^[^\u0000-\u0009\u000B-\u001F\u007F\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]*$/;
20356
+ var renderableText = /* @__PURE__ */ __name((min, max) => {
20357
+ return external_exports.string().min(min).max(max).regex(RENDERABLE_TEXT, "must not carry control or bidirectional-override characters");
20358
+ }, "renderableText");
20351
20359
  var httpsUrl = /* @__PURE__ */ __name((field) => {
20352
20360
  return external_exports.url().max(2048).refine((value) => value.startsWith("https://"), `${field} must be an HTTPS URL`);
20353
20361
  }, "httpsUrl");
@@ -20483,6 +20491,35 @@ var validateOauth = /* @__PURE__ */ __name((input2, ctx) => {
20483
20491
  }
20484
20492
  }, "validateOauth");
20485
20493
 
20494
+ // ../../libs/app-manifest/src/roles.ts
20495
+ var MAX_APP_ROLE_DESCRIPTION = 200;
20496
+ var roleSchema = external_exports.object({
20497
+ key: external_exports.enum(DECLARABLE_APP_ROLES, {
20498
+ error: /* @__PURE__ */ __name((issue2) => {
20499
+ return issue2.input === "full" ? "full access is always available and is not declared" : void 0;
20500
+ }, "error")
20501
+ }),
20502
+ description: renderableText(1, MAX_APP_ROLE_DESCRIPTION).optional()
20503
+ }).strict();
20504
+ var rolesSchema = external_exports.array(roleSchema).max(DECLARABLE_APP_ROLES.length).superRefine((roles, ctx) => {
20505
+ uniqueOrIssue(
20506
+ ctx,
20507
+ roles.map((role) => role.key),
20508
+ [],
20509
+ "role"
20510
+ );
20511
+ }).transform((roles) => {
20512
+ return [...roles].sort((a, b) => APP_ROLES.indexOf(a.key) - APP_ROLES.indexOf(b.key));
20513
+ });
20514
+ var validateRoles = /* @__PURE__ */ __name((manifest, ctx) => {
20515
+ const declared = /* @__PURE__ */ new Set(["full", ...(manifest.roles ?? []).map((role) => role.key)]);
20516
+ for (const [index, tool] of manifest.tools.entries()) {
20517
+ if (tool.role !== void 0 && !declared.has(tool.role)) {
20518
+ ctx.addIssue({ code: "custom", path: ["tools", index, "role"], message: `Tool role ${tool.role} is not declared in roles` });
20519
+ }
20520
+ }
20521
+ }, "validateRoles");
20522
+
20486
20523
  // ../../libs/app-manifest/src/route.ts
20487
20524
  var ENDPOINT_PATH_PATTERN = /^\/(?:(?::[a-zA-Z][a-zA-Z0-9_]*|[a-zA-Z0-9_-]+)(?:\/(?::[a-zA-Z][a-zA-Z0-9_]*|[a-zA-Z0-9_-]+))*)?$/;
20488
20525
  var isParam = /* @__PURE__ */ __name((segment) => {
@@ -20579,10 +20616,6 @@ var MAX_MANIFEST_TOOLS_BYTES = 150 * 1024;
20579
20616
  var TOOL_PROPERTY_NAME = /^[a-zA-Z][a-zA-Z0-9_]*$/;
20580
20617
  var RESERVED_TOOL_ARGUMENTS = ["teamId", "siteId", "installId"];
20581
20618
  var TOOL_STRING_FORMATS = ["uuid", "date", "date-time", "email", "uri"];
20582
- var RENDERABLE_TEXT = /^[^\u0000-\u0009\u000B-\u001F\u007F\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]*$/;
20583
- var renderableText = /* @__PURE__ */ __name((min, max) => {
20584
- return external_exports.string().min(min).max(max).regex(RENDERABLE_TEXT, "must not carry control or bidirectional-override characters");
20585
- }, "renderableText");
20586
20619
  var propertyName = external_exports.string().min(1).max(48).regex(TOOL_PROPERTY_NAME);
20587
20620
  var FORMAT_SCHEMAS = {
20588
20621
  uuid: external_exports.uuid(),
@@ -20678,7 +20711,8 @@ var toolSchema = external_exports.object({
20678
20711
  title: renderableText(1, MAX_APP_TOOL_TITLE_LENGTH),
20679
20712
  description: renderableText(MIN_APP_TOOL_DESCRIPTION, MAX_APP_TOOL_DESCRIPTION),
20680
20713
  inputSchema: toolInputSchema,
20681
- annotations: toolAnnotationsSchema
20714
+ annotations: toolAnnotationsSchema,
20715
+ role: external_exports.enum(APP_ROLES).optional()
20682
20716
  }).strict();
20683
20717
  var nodeChildren = /* @__PURE__ */ __name((value) => {
20684
20718
  const properties2 = value["properties"];
@@ -21082,6 +21116,9 @@ var baseSchema = external_exports.object({
21082
21116
  description: "The push topics this app broadcasts to every app on the site. Requires the `sharedContext` data capability; a topic left out reaches only this app\u2019s own browser code."
21083
21117
  }),
21084
21118
  endpoints: external_exports.array(endpointSchema).max(16).default([]).meta({ description: "The HTTP routes served at https://<site>/app/<slug><path>." }),
21119
+ roles: rolesSchema.optional().meta({
21120
+ description: "The app roles this app uses, from the fixed list owner, admin, manager, editor, member, viewer. Full access is always available and is not declared. A team member is given one app role per app."
21121
+ }),
21085
21122
  tools: toolsSchema.meta({
21086
21123
  description: "The MCP tools this app serves to AI assistants, published as `<slug>_<name>`. Each one a site manager accepts becomes callable on their behalf by an assistant."
21087
21124
  }),
@@ -21438,6 +21475,7 @@ var appManifestSchema = baseSchema.superRefine((manifest, ctx) => {
21438
21475
  validateOauth(manifest, ctx);
21439
21476
  validateServer(manifest, ctx);
21440
21477
  validateTools(manifest.tools, ctx);
21478
+ validateRoles(manifest, ctx);
21441
21479
  });
21442
21480
 
21443
21481
  // ../../libs/app-manifest/src/oauth-flow.ts
@@ -21708,7 +21746,12 @@ var toolSchema2 = external_exports.object({
21708
21746
  name: external_exports.string().min(1).max(MAX_APP_TOOL_NAME_LENGTH).regex(APP_TOOL_NAME_PATTERN),
21709
21747
  // Who asked, and what they may do on this site. Derived from the bearer by the portal, never
21710
21748
  // from the model's arguments.
21711
- viewer: external_exports.object({ userId: external_exports.string().min(1).max(64), access: external_exports.enum(["read", "write"]) }).strict(),
21749
+ viewer: external_exports.object({
21750
+ userId: external_exports.string().min(1).max(64),
21751
+ access: external_exports.enum(["read", "write"]),
21752
+ // Optional only while a site worker or app runtime that predates it can still receive this envelope; absent means `full`.
21753
+ appRole: external_exports.enum(APP_ROLES).optional()
21754
+ }).strict(),
21712
21755
  // Already validated against the tool's declared inputSchema, with the reserved routing names
21713
21756
  // stripped. Every declared input schema has an object root, so this is an object too — and a
21714
21757
  // call that names no arguments normalizes to `{}` rather than reaching the handler as undefined.
@@ -21914,7 +21957,12 @@ var MAX_UI_BODY_BYTES = 1024 * 1024;
21914
21957
  var uiInstallSchema = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100), enabled: external_exports.boolean() }).strict();
21915
21958
  var uiContextSchema = external_exports.object({
21916
21959
  version: external_exports.string().min(1).max(32),
21917
- viewer: external_exports.object({ userId: external_exports.string().min(1).max(64), access: external_exports.enum(["read", "write"]) }).strict(),
21960
+ viewer: external_exports.object({
21961
+ userId: external_exports.string().min(1).max(64),
21962
+ access: external_exports.enum(["read", "write"]),
21963
+ // Optional only while a site worker that predates it can still send this header; absent means `full`.
21964
+ appRole: external_exports.enum(APP_ROLES).optional()
21965
+ }).strict(),
21918
21966
  install: uiInstallSchema,
21919
21967
  installs: external_exports.array(uiInstallSchema).min(1).max(MAX_INSTALLS_PER_APP),
21920
21968
  writeTicket: external_exports.string().min(1).max(2048).optional()
@@ -29738,7 +29786,9 @@ var claimsSchema = external_exports.object({
29738
29786
  appId: external_exports.string().min(1),
29739
29787
  appInstallId: external_exports.string().min(1),
29740
29788
  appVersionId: external_exports.string().min(1),
29741
- access: external_exports.enum(["read", "write"])
29789
+ access: external_exports.enum(["read", "write"]),
29790
+ // Absent means `full`; see the mint below.
29791
+ appRole: external_exports.enum(APP_ROLES).optional()
29742
29792
  }).strict();
29743
29793
  var appUiAudience = /* @__PURE__ */ __name((slug) => {
29744
29794
  return `app-ui:${slug}`;
@@ -29907,7 +29957,8 @@ var forwardToApp2 = /* @__PURE__ */ __name(async (c, input2) => {
29907
29957
  "app.install_ord": install.ord,
29908
29958
  "app.install_label": install.label,
29909
29959
  "app.viewer_id": viewer.userId,
29910
- "app.viewer_access": viewer.access
29960
+ "app.viewer_access": viewer.access,
29961
+ "app.viewer_app_role": viewer.appRole
29911
29962
  }
29912
29963
  },
29913
29964
  () => {
@@ -29953,7 +30004,13 @@ var authorizeUi = /* @__PURE__ */ __name(async (c, slug, origin) => {
29953
30004
  appId: verdict.claims.appId,
29954
30005
  appVersionId: verdict.claims.appVersionId
29955
30006
  }) : void 0;
29956
- return { app: app2, install, viewer: { userId: verdict.claims.userId, access: verdict.claims.access }, writeTicket };
30007
+ return {
30008
+ app: app2,
30009
+ install,
30010
+ // The portal mints `full` as no claim, so a site worker that predates the claim still accepts the token.
30011
+ viewer: { userId: verdict.claims.userId, access: verdict.claims.access, appRole: verdict.claims.appRole ?? "full" },
30012
+ writeTicket
30013
+ };
29957
30014
  }, "authorizeUi");
29958
30015
  var handleAppUi = /* @__PURE__ */ __name(async (c, slug) => {
29959
30016
  const host = c.env.APP_UI_HOST;
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "main": {
3
- "version": "0.1.5",
4
- "gitSha": "7c31ca4765a91cfd5098f098208c2409638b6e24",
5
- "builtAt": "2026-09-25T12:23:20.370Z",
3
+ "version": "0.1.6",
4
+ "gitSha": "802179565243bb2e4dd99f79f00a3815d4f0568c",
5
+ "builtAt": "2026-09-28T04:28:30.340Z",
6
6
  "compatibilityDate": "2026-06-23",
7
7
  "compatibilityFlags": [
8
8
  "nodejs_compat"
@@ -39,9 +39,9 @@
39
39
  ]
40
40
  },
41
41
  "app-runtime": {
42
- "version": "0.1.3",
43
- "gitSha": "5991b0352059c2c9701ccc4e3414dc93fbba3371",
44
- "builtAt": "2026-09-25T12:23:22.651Z",
42
+ "version": "0.1.4",
43
+ "gitSha": "b53a595d63827d8fda4f898391c6c0986f0fd1fa",
44
+ "builtAt": "2026-09-28T04:28:33.821Z",
45
45
  "compatibilityDate": "2026-06-23",
46
46
  "compatibilityFlags": [
47
47
  "nodejs_compat"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blotout/qinto-cli",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "description": "Qinto CLI: authenticate against the Qinto platform and operate it from the terminal or from an agent.",
6
6
  "repository": {