@bike4mind/cli 0.16.0 → 0.17.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.
@@ -590,7 +590,8 @@ const b4mLLMTools = z.enum([
590
590
  "navigate_view",
591
591
  "generate_jupyter_notebook",
592
592
  "excel_generation",
593
- "fmp_financial_data"
593
+ "fmp_financial_data",
594
+ "skill"
594
595
  ]);
595
596
  b4mLLMTools.options.map((tool) => tool);
596
597
  /**
@@ -902,6 +903,45 @@ const ClaudeArtifactMimeTypes = {
902
903
  PYTHON: "application/vnd.ant.python",
903
904
  BLOG_DRAFT: "application/vnd.b4m.blog-draft"
904
905
  };
906
+ /**
907
+ * Map a MIME type (or AI-provider artifact-type string) to an internal {@link ArtifactType}.
908
+ *
909
+ * Single source of truth — consumed by the artifact parsers (b4m-core/utils + client) and the
910
+ * tool_result dedup in ChatCompletionProcess. Exact blessed-type matches first (case-insensitive,
911
+ * since MIME types are), then language/format inference; returns null if unrecognized.
912
+ *
913
+ * #8907: this previously lived as three hand-maintained copies that drifted — e.g. the lattice
914
+ * tool emits `application/vnd.b4m.lattice` but a copy matched `application/vnd.ant.lattice`,
915
+ * letting lattice tool_result artifacts dodge the dedup set (#8905).
916
+ */
917
+ function mapMimeTypeToArtifactType(mimeType) {
918
+ if (!mimeType) return null;
919
+ const normalized = mimeType.toLowerCase().trim();
920
+ switch (normalized) {
921
+ case ClaudeArtifactMimeTypes.REACT.toLowerCase(): return "react";
922
+ case ClaudeArtifactMimeTypes.HTML.toLowerCase(): return "html";
923
+ case ClaudeArtifactMimeTypes.SVG.toLowerCase(): return "svg";
924
+ case ClaudeArtifactMimeTypes.MERMAID.toLowerCase(): return "mermaid";
925
+ case ClaudeArtifactMimeTypes.RECHARTS.toLowerCase(): return "recharts";
926
+ case ClaudeArtifactMimeTypes.CHESS.toLowerCase(): return "chess";
927
+ case ClaudeArtifactMimeTypes.CODE.toLowerCase(): return "code";
928
+ case ClaudeArtifactMimeTypes.MARKDOWN.toLowerCase(): return "code";
929
+ case ClaudeArtifactMimeTypes.LATTICE.toLowerCase(): return "lattice";
930
+ case ClaudeArtifactMimeTypes.PYTHON.toLowerCase(): return "python";
931
+ case ClaudeArtifactMimeTypes.BLOG_DRAFT.toLowerCase(): return "blog-draft";
932
+ }
933
+ if (normalized.includes("jsx") || normalized.includes("react")) return "react";
934
+ if (normalized.includes("javascript") || normalized.includes("typescript")) return "code";
935
+ if (normalized.includes("python") || normalized === "text/x-python") return "python";
936
+ if (normalized.includes("java") || normalized.includes("c++") || normalized.includes("rust") || normalized.includes("go") || normalized.includes("ruby") || normalized.includes("php") || normalized.includes("swift") || normalized.includes("kotlin") || normalized.includes("csharp") || normalized.includes("c#")) return "code";
937
+ if (normalized.includes("html") || normalized.includes("xhtml")) return "html";
938
+ if (normalized.includes("svg")) return "svg";
939
+ if (normalized.includes("markdown") || normalized.includes("md")) return "code";
940
+ if (normalized.includes("mermaid")) return "mermaid";
941
+ if (normalized.includes("recharts") || normalized.includes("chart")) return "recharts";
942
+ if (normalized.includes("chess")) return "chess";
943
+ return null;
944
+ }
905
945
  let KnowledgeType = /* @__PURE__ */ function(KnowledgeType) {
906
946
  /**
907
947
  * A knowledge that is from a URL.
@@ -1275,7 +1315,7 @@ const SreRepoConfigSchema = z.object({
1275
1315
  /** Whether this repo's SRE pipeline is active */
1276
1316
  enabled: z.boolean().default(false),
1277
1317
  /** LLM model ID for Diagnostician */
1278
- modelId: z.string().default("claude-sonnet-4-20250514"),
1318
+ modelId: z.string().default("claude-sonnet-4-6"),
1279
1319
  /** Max diff lines per fix */
1280
1320
  maxDiffLines: z.number().min(1).default(50),
1281
1321
  /** Max fixes dispatched per day for this repo */
@@ -3628,11 +3668,22 @@ const OpenAIImageStyleSchema = z.enum(["vivid", "natural"]);
3628
3668
  * and they are structural-control models — silently remapping them to a plain
3629
3669
  * text-to-image model would drop the control image and return the wrong kind of result,
3630
3670
  * so a clear "unsupported model" error is the more honest response.
3671
+ *
3672
+ * The three `grok-2-image*` ids are the prior xAI image-model ids, all superseded
3673
+ * in the enum by `grok-imagine-image-quality`. The id went through an un-aliased
3674
+ * rename chain — `grok-2-image-1212` (original) → `grok-2-image` → `grok-2-image-gen`
3675
+ * → `grok-imagine-image-quality` — so any of the three could survive in stale
3676
+ * persisted client state (`grok-2-image-1212` was the one observed in #9211). All are
3677
+ * xAI text-to-image models, so remapping to the current id is a faithful
3678
+ * same-provider, same-modality replacement.
3631
3679
  */
3632
3680
  const LEGACY_IMAGE_MODEL_MAP = {
3633
3681
  "dall-e-3": "gpt-image-1",
3634
3682
  "dall-e-2": "gpt-image-1",
3635
- "flux-dev": "flux-pro-1.1"
3683
+ "flux-dev": "flux-pro-1.1",
3684
+ "grok-2-image-1212": "grok-imagine-image-quality",
3685
+ "grok-2-image": "grok-imagine-image-quality",
3686
+ "grok-2-image-gen": "grok-imagine-image-quality"
3636
3687
  };
3637
3688
  const OpenAIImageGenerationInput = z.object({
3638
3689
  prompt: z.string(),
@@ -3929,8 +3980,36 @@ const OrchestrationDefaultsSchema = z.object({
3929
3980
  "code_execute",
3930
3981
  "coordinate_task"
3931
3982
  ]),
3932
- /** Tool names explicitly forbidden. Empty by default — `allowedTools` is the whitelist. */
3933
- deniedTools: z.array(z.string()).default([]),
3983
+ /**
3984
+ * Tool names explicitly forbidden. Enforced as a final subtraction in
3985
+ * `pickEffectiveEnabledTools` — wins even over payload-pinned tools — so this
3986
+ * is the defense-in-depth backstop for the case where an admin broadens
3987
+ * `allowedTools` without realizing a parallel denylist is also needed.
3988
+ *
3989
+ * Seeded with every tool that mutates user data (#8945, the spec's
3990
+ * "anything tagged `mutates_user_data`"): destructive/overwriting filesystem
3991
+ * writes, arbitrary shell execution, blog authoring/publishing, persisted
3992
+ * lattice-model writes, and storage-backed artifact generation. New mutating
3993
+ * tools should be added here until the tag-derived denylist (Option B, #8923/
3994
+ * #8924) replaces this hand-curated list.
3995
+ */
3996
+ deniedTools: z.array(z.string()).default([
3997
+ "create_file",
3998
+ "edit_file",
3999
+ "edit_local_file",
4000
+ "delete_file",
4001
+ "bash_execute",
4002
+ "blog_draft",
4003
+ "blog_edit",
4004
+ "blog_publish",
4005
+ "lattice_create_model",
4006
+ "lattice_add_entity",
4007
+ "lattice_set_value",
4008
+ "lattice_create_rule",
4009
+ "image_generation",
4010
+ "edit_image",
4011
+ "excel_generation"
4012
+ ]),
3934
4013
  /** Per-thoroughness iteration ceiling. Matches `IAgent.maxIterations`. */
3935
4014
  maxIterations: z.object({
3936
4015
  quick: z.number().int().positive(),
@@ -7261,6 +7340,13 @@ const PromptMetaZodSchema = z.object({
7261
7340
  generatedImageReferences: z.array(z.string()).optional(),
7262
7341
  promptErrors: z.array(z.string()).optional(),
7263
7342
  warnings: z.array(z.string()).optional(),
7343
+ /**
7344
+ * The provider's reason for ending generation (Anthropic vocabulary:
7345
+ * 'end_turn' | 'max_tokens' | 'tool_use' | 'stop_sequence' | 'pause_turn').
7346
+ * 'max_tokens' signals the response was truncated against the output ceiling,
7347
+ * letting the client render a truncated-artifact recovery affordance (#9259).
7348
+ */
7349
+ finishReason: z.string().optional(),
7264
7350
  statusLog: z.array(z.object({
7265
7351
  status: z.string(),
7266
7352
  timestamp: z.date().or(z.string())
@@ -7982,12 +8068,14 @@ z$1.object({
7982
8068
  slug: z$1.string().min(2).max(60).regex(slugRegex, "Slug must be lowercase alphanumeric with hyphens (e.g. \"my-data-lake\")"),
7983
8069
  description: z$1.string().max(2e3).optional(),
7984
8070
  fileTagPrefix: z$1.string().min(2).max(30).refine((s) => s.endsWith(":"), "Tag prefix must end with \":\" (e.g. \"acme:\")"),
7985
- requiredUserTag: z$1.string().min(1).max(100).optional()
8071
+ requiredUserTag: z$1.string().min(1).max(100).optional(),
8072
+ requiredEntitlement: z$1.string().min(3).max(100).refine((s) => s.includes(":") && s.split(":").every((part) => part.length > 0), "Entitlement key must be namespaced with non-empty parts (e.g. \"product:pro\")").optional()
7986
8073
  });
7987
8074
  z$1.object({
7988
8075
  name: z$1.string().min(1).max(200).optional(),
7989
8076
  description: z$1.string().max(2e3).optional(),
7990
- requiredUserTag: z$1.string().min(1).max(100).optional()
8077
+ requiredUserTag: z$1.string().min(1).max(100).optional(),
8078
+ requiredEntitlement: z$1.string().min(3).max(100).refine((s) => s.includes(":") && s.split(":").every((part) => part.length > 0), "Entitlement key must be namespaced with non-empty parts (e.g. \"product:pro\")").optional()
7991
8079
  });
7992
8080
  z$1.object({
7993
8081
  organizationId: z$1.string().optional(),
@@ -8133,37 +8221,68 @@ const DATA_LAKES = [{
8133
8221
  id: "ionq-sales",
8134
8222
  name: "IonQ Sales Intelligence",
8135
8223
  requiredUserTag: "Opti",
8224
+ requiredEntitlement: "optihashi:pro",
8136
8225
  fileTagPrefix: "ionq:",
8137
8226
  datalakeTag: "datalake:ionq-sales"
8138
8227
  }, {
8139
8228
  id: "opti-knowledge",
8140
8229
  name: "Optimization Knowledge Base",
8141
8230
  requiredUserTag: "Opti",
8231
+ requiredEntitlement: "optihashi:pro",
8142
8232
  fileTagPrefix: "opti:",
8143
8233
  datalakeTag: "datalake:opti-knowledge"
8144
8234
  }];
8145
8235
  /**
8146
- * Returns data lakes accessible to a user based on their tags.
8147
- * Data lakes without a requiredUserTag are accessible to all authenticated users.
8148
- * When dynamicDataLakes is provided (fetched from DB), those take precedence
8149
- * over hardcoded DATA_LAKES entries with the same id.
8236
+ * Canonical normalization for entitlement keys + `requiredEntitlement` values the ONE
8237
+ * rule, applied at write time (create/update/stamp) and at match time. Mirrors the
8238
+ * entitlement registry's `normalizeTag` (trim + lowercase) so a value authored in any
8239
+ * casing matches the lowercase keys the resolver produces.
8240
+ */
8241
+ const normalizeEntitlementKey = (key) => key.trim().toLowerCase();
8242
+ /**
8243
+ * Single projection from a persisted lake document to the lightweight DataLakeConfig
8244
+ * the access filters operate on. Centralized so the `requiredEntitlement` field (and any
8245
+ * future field) cannot be silently dropped at one of the many former inline projections.
8246
+ */
8247
+ function toDataLakeConfig(dl) {
8248
+ return {
8249
+ id: dl.id,
8250
+ name: dl.name,
8251
+ requiredUserTag: dl.requiredUserTag,
8252
+ requiredEntitlement: dl.requiredEntitlement,
8253
+ fileTagPrefix: dl.fileTagPrefix,
8254
+ datalakeTag: dl.datalakeTag
8255
+ };
8256
+ }
8257
+ /**
8258
+ * Returns data lakes accessible to a user.
8259
+ *
8260
+ * Access rule (generic, any-of declared requirements): a lake is accessible iff it
8261
+ * declares NO access requirement, OR the user satisfies ANY declared requirement —
8262
+ * either `requiredUserTag` (matched against the user's tags) or `requiredEntitlement`
8263
+ * (matched against the caller's resolved `entitlementKeys`). A lake declaring an
8264
+ * entitlement but no tag is therefore NOT public (it is gated by the key).
8265
+ *
8266
+ * Data lakes without any requirement are accessible to all authenticated users.
8267
+ * When dynamicDataLakes is provided (fetched from DB), those take precedence over
8268
+ * hardcoded DATA_LAKES entries with the same id. `entitlementKeys` is optional — callers
8269
+ * that don't resolve entitlements (tag-only surfaces) omit it and get tag-only matching.
8150
8270
  */
8151
- function getAccessibleDataLakes(userTags, dynamicDataLakes) {
8271
+ function getAccessibleDataLakes(userTags, dynamicDataLakes, entitlementKeys) {
8152
8272
  const normalizedUserTags = userTags.map((tag) => tag.toLowerCase());
8273
+ const normalizedKeys = (entitlementKeys ?? []).map(normalizeEntitlementKey);
8153
8274
  let allLakes;
8154
8275
  if (dynamicDataLakes && dynamicDataLakes.length > 0) {
8155
8276
  const dynamicIds = new Set(dynamicDataLakes.map((d) => d.id));
8156
8277
  const fallbacks = DATA_LAKES.filter((dl) => !dynamicIds.has(dl.id));
8157
8278
  allLakes = [...dynamicDataLakes, ...fallbacks];
8158
8279
  } else allLakes = DATA_LAKES;
8159
- return allLakes.filter((dl) => !dl.requiredUserTag || normalizedUserTags.includes(dl.requiredUserTag.toLowerCase()));
8160
- }
8161
- /**
8162
- * Returns the datalake: meta-tags for all data lakes a user can access.
8163
- * Pass dynamicDataLakes from DB for runtime-registered data lakes.
8164
- */
8165
- function getDataLakeTags(userTags, dynamicDataLakes) {
8166
- return getAccessibleDataLakes(userTags, dynamicDataLakes).map((dl) => dl.datalakeTag);
8280
+ return allLakes.filter((dl) => {
8281
+ if (!(!!dl.requiredUserTag || !!dl.requiredEntitlement)) return true;
8282
+ const tagMatch = !!dl.requiredUserTag && normalizedUserTags.includes(dl.requiredUserTag.toLowerCase());
8283
+ const entMatch = !!dl.requiredEntitlement && normalizedKeys.includes(normalizeEntitlementKey(dl.requiredEntitlement));
8284
+ return tagMatch || entMatch;
8285
+ });
8167
8286
  }
8168
8287
  /**
8169
8288
  * Jupyter Kernel Constants and Validation
@@ -8316,6 +8435,8 @@ z.object({
8316
8435
  /** Notebook session ID */
8317
8436
  sessionId: z.string(),
8318
8437
  historyCount: z.number(),
8438
+ /** Epoch ms when the client submitted the prompt (for the request-lifecycle status log). */
8439
+ clientSubmittedAt: z.number().optional(),
8319
8440
  imageConfig: GenerateImageToolCallSchema.optional(),
8320
8441
  deepResearchConfig: z.object({
8321
8442
  maxDepth: z.number().optional(),
@@ -8596,6 +8717,114 @@ BaseArtifactSchema.extend({
8596
8717
  })
8597
8718
  });
8598
8719
  /**
8720
+ * Annotation schemas — the collaboration layer that sits on top of a
8721
+ * PublishedArtifact. An annotation is a structured input keyed to an artifact
8722
+ * by its `publicId`. The `kind` discriminator is deliberately generic: v1 ships
8723
+ * only `comment`, but `approval`/`vote`/`signature` are reserved so the layer
8724
+ * can grow into approval workflows and e-signature without a model migration.
8725
+ *
8726
+ * Anchoring is PINS-FIRST: an annotation may carry normalized viewport
8727
+ * coordinates (0..1) and a best-effort CSS selector. Robust element-range
8728
+ * anchoring is deferred. An annotation with no anchor is document-level.
8729
+ *
8730
+ * Every annotation records the artifact version it was made against
8731
+ * (`artifactVersionSha`, the artifact's `sha256Index` at the time) so feedback
8732
+ * stays pinned to the version it critiqued — this is also the input the AI
8733
+ * revision loop (Phase C) consumes.
8734
+ */
8735
+ const AnnotationKindSchema = z.enum([
8736
+ "comment",
8737
+ "approval",
8738
+ "vote",
8739
+ "signature"
8740
+ ]);
8741
+ /** Owner-controlled gate on WHO may annotate, orthogonal to artifact visibility
8742
+ * (which controls who may VIEW). `none` = read-only (today's behavior, default).
8743
+ * `open` = any authenticated viewer who passes the visibility gate. `restricted`
8744
+ * = owner + explicit allowlist (allowlist UI deferred). */
8745
+ const CommentPolicySchema = z.enum([
8746
+ "none",
8747
+ "open",
8748
+ "restricted"
8749
+ ]);
8750
+ const AnnotationAnchorSchema = z.object({
8751
+ /** Normalized horizontal position within the artifact viewport, 0..1. */
8752
+ x: z.number().min(0).max(1).optional(),
8753
+ /** Normalized vertical position within the artifact document, 0..1. */
8754
+ y: z.number().min(0).max(1).optional(),
8755
+ /** Best-effort CSS selector path captured by the widget (may be stale). */
8756
+ selector: z.string().max(1024).optional(),
8757
+ /** Optional scroll-section label / fragment the pin was dropped in. */
8758
+ scrollSection: z.string().max(256).optional()
8759
+ });
8760
+ const AnnotationPayloadSchema = z.object({
8761
+ /** kind === 'approval' */
8762
+ decision: z.enum(["approve", "reject"]).optional(),
8763
+ /** kind === 'vote' */
8764
+ choice: z.string().max(256).optional(),
8765
+ /** kind === 'signature' */
8766
+ signedName: z.string().max(200).optional(),
8767
+ signatureHash: z.string().max(128).optional()
8768
+ }).optional();
8769
+ const ANNOTATION_BODY_MAX = 5e3;
8770
+ z.object({
8771
+ id: z.string(),
8772
+ /** FK → PublishedArtifact.publicId. */
8773
+ publicId: z.string(),
8774
+ /** PublishedArtifact.sha256Index at the time the annotation was made. */
8775
+ artifactVersionSha: z.string().optional(),
8776
+ kind: AnnotationKindSchema.prefault("comment"),
8777
+ authorId: z.string(),
8778
+ /** Denormalized for render — annotation authorship is immutable. */
8779
+ authorDisplayName: z.string().max(200),
8780
+ body: z.string().max(ANNOTATION_BODY_MAX),
8781
+ anchor: AnnotationAnchorSchema.optional(),
8782
+ /** null/absent = top-level; else this is a reply to another annotation. */
8783
+ threadRootId: z.string().nullish(),
8784
+ payload: AnnotationPayloadSchema,
8785
+ resolvedAt: z.date().nullish(),
8786
+ resolvedBy: z.string().nullish(),
8787
+ createdAt: z.date(),
8788
+ updatedAt: z.date(),
8789
+ deletedAt: z.date().nullish(),
8790
+ deletedBy: z.string().nullish()
8791
+ });
8792
+ z.object({
8793
+ body: z.string().min(1).max(ANNOTATION_BODY_MAX),
8794
+ /** v1 accepts only 'comment'; defaulted server-side. */
8795
+ kind: AnnotationKindSchema.optional(),
8796
+ anchor: AnnotationAnchorSchema.optional(),
8797
+ threadRootId: z.string().optional(),
8798
+ artifactVersionSha: z.string().optional(),
8799
+ payload: AnnotationPayloadSchema
8800
+ });
8801
+ z.object({
8802
+ body: z.string().min(1).max(ANNOTATION_BODY_MAX).optional(),
8803
+ /** Toggle resolution. true → resolve, false → reopen. */
8804
+ resolved: z.boolean().optional()
8805
+ }).refine((v) => v.body !== void 0 || v.resolved !== void 0, { message: "Nothing to update" });
8806
+ const AnnotationDtoSchema = z.object({
8807
+ id: z.string(),
8808
+ publicId: z.string(),
8809
+ kind: AnnotationKindSchema,
8810
+ authorId: z.string(),
8811
+ authorDisplayName: z.string(),
8812
+ body: z.string(),
8813
+ anchor: AnnotationAnchorSchema.optional(),
8814
+ threadRootId: z.string().nullish(),
8815
+ payload: AnnotationPayloadSchema,
8816
+ artifactVersionSha: z.string().optional(),
8817
+ resolvedAt: z.string().nullish(),
8818
+ createdAt: z.string()
8819
+ });
8820
+ z.object({
8821
+ annotations: z.array(AnnotationDtoSchema),
8822
+ /** Echoed so the widget can render the right affordance without a 2nd call. */
8823
+ commentPolicy: CommentPolicySchema,
8824
+ /** Whether the CURRENT caller may create an annotation right now. */
8825
+ canComment: z.boolean()
8826
+ });
8827
+ /**
8599
8828
  * Published-artifact schemas — the B4M instantiation of the `artifact-publishing`
8600
8829
  * blueprint (MillionOnMars/blueprints, extracted from Polaris Publish v1).
8601
8830
  *
@@ -8666,6 +8895,52 @@ const PUBLISH_LIMITS = {
8666
8895
  maxBundleBytes: 50 * 1024 * 1024,
8667
8896
  maxFileBytes: 10 * 1024 * 1024
8668
8897
  };
8898
+ /**
8899
+ * Lifecycle from a moderation standpoint:
8900
+ * active — published, no open reports
8901
+ * reported — one or more abuse reports filed; awaiting admin review
8902
+ * taken_down — an admin removed it (also soft-deleted, so it 404s at serve)
8903
+ */
8904
+ const ModerationStatusSchema = z.enum([
8905
+ "active",
8906
+ "reported",
8907
+ "taken_down"
8908
+ ]);
8909
+ /** Why a viewer flagged a public page. */
8910
+ const ReportReasonSchema = z.enum([
8911
+ "spam",
8912
+ "phishing",
8913
+ "malware",
8914
+ "abuse",
8915
+ "copyright",
8916
+ "other"
8917
+ ]);
8918
+ /** Open / resolved state of an individual report record. */
8919
+ const ReportStatusSchema = z.enum([
8920
+ "open",
8921
+ "actioned",
8922
+ "dismissed"
8923
+ ]);
8924
+ z.object({
8925
+ /** The reported artifact's short id (the `/p/...` identifier). */
8926
+ publicId: z.string(),
8927
+ /** Mongo _id of the reported artifact, denormalized for admin joins. */
8928
+ artifactId: z.string(),
8929
+ /** Reporter's user id; null for anonymous/unauthenticated reports. */
8930
+ reporterId: z.string().nullish(),
8931
+ reason: ReportReasonSchema,
8932
+ details: z.string().max(2e3).optional(),
8933
+ status: ReportStatusSchema.prefault("open"),
8934
+ /** Admin who actioned/dismissed the report. */
8935
+ resolvedBy: z.string().nullish(),
8936
+ resolvedAt: z.date().nullish(),
8937
+ createdAt: z.date(),
8938
+ updatedAt: z.date()
8939
+ });
8940
+ z.object({
8941
+ reason: ReportReasonSchema,
8942
+ details: z.string().max(2e3).optional()
8943
+ });
8669
8944
  z.object({
8670
8945
  /** Short opaque id for short URLs (`/p/r/{publicId}`, `/p/f/{publicId}`) and lookups. */
8671
8946
  publicId: z.string(),
@@ -8677,6 +8952,9 @@ z.object({
8677
8952
  visibility: VisibilitySchema.prefault("private"),
8678
8953
  /** Group id a viewer must belong to when gated cross-scope. */
8679
8954
  gatedToGroupId: z.string().optional(),
8955
+ /** Collaboration gate: who (among viewers) may annotate. Orthogonal to
8956
+ * `visibility`. Defaults to `none` (read-only) until the owner opts in. */
8957
+ commentPolicy: CommentPolicySchema.prefault("none"),
8680
8958
  ownerId: z.string(),
8681
8959
  lastPublishedBy: z.string().optional(),
8682
8960
  source: PublishSourceSchema,
@@ -8694,6 +8972,13 @@ z.object({
8694
8972
  publishedAt: z.date(),
8695
8973
  previousVersionMeta: ArtifactVersionMetaSchema.optional(),
8696
8974
  viewCount: z.int().nonnegative().prefault(0),
8975
+ /** Concurrency lock for AI revise (set while a revision is in flight). */
8976
+ revisingAt: z.date().nullish(),
8977
+ /** Moderation state (issue #8733). */
8978
+ moderationStatus: ModerationStatusSchema.prefault("active"),
8979
+ reportCount: z.int().nonnegative().prefault(0),
8980
+ /** Set when an admin takes the page down (alongside soft-delete). */
8981
+ takedownReason: z.string().max(1e3).nullish(),
8697
8982
  createdAt: z.date(),
8698
8983
  updatedAt: z.date(),
8699
8984
  deletedAt: z.date().nullish(),
@@ -8728,6 +9013,8 @@ z.object({
8728
9013
  description: z.string().max(1e3).optional(),
8729
9014
  visibility: VisibilitySchema.optional(),
8730
9015
  gatedToGroupId: z.string().optional(),
9016
+ /** Who may annotate the published artifact. Defaults to `none` (read-only). */
9017
+ commentPolicy: CommentPolicySchema.optional(),
8731
9018
  source: PublishSourceSchema.optional(),
8732
9019
  files: z.array(FileDescriptorSchema).min(1).max(PUBLISH_LIMITS.maxFiles)
8733
9020
  });
@@ -8883,6 +9170,33 @@ function isGPTImage2Model(model) {
8883
9170
  return model === "gpt-image-2" || model.startsWith("gpt-image-2");
8884
9171
  }
8885
9172
  /**
9173
+ * Whether a user can access a model. Access is any-of (mirrors the Q3b data-lake
9174
+ * rule, `getAccessibleDataLakes`): a non-admin reaches the model via
9175
+ * `allowedUserTags ∩ userTags` OR `allowedEntitlements ∩ entitlementKeys`.
9176
+ *
9177
+ * `entitlementKeys` is optional — when omitted/empty the entitlement branch is
9178
+ * inert, so a model with no `allowedEntitlements` behaves exactly as before
9179
+ * (tag-only). This lets a tag-less subscriber reach an entitlement-gated model
9180
+ * while leaving every existing tag-gated model unchanged (zero regression).
9181
+ *
9182
+ * Pure + zero-dependency (only types + the shared key normalizer), so it lives
9183
+ * in `@bike4mind/common` as the SINGLE source of truth — imported by the core
9184
+ * `@bike4mind/utils` re-export (server/services) AND the client
9185
+ * `useAccessibleModels` hook, which previously kept a hand-rolled twin "to avoid
9186
+ * AWS SDK imports". Common is browser-safe, so there is no longer any reason to
9187
+ * duplicate the logic.
9188
+ */
9189
+ function isModelAccessible(model, userTags, isAdmin = false, entitlementKeys = []) {
9190
+ if (!model.enabled) return false;
9191
+ if (isAdmin) return true;
9192
+ const normalizedUserTags = userTags.map((tag) => tag.toLowerCase());
9193
+ const normalizedAllowedTags = (model.allowedUserTags ?? []).map((tag) => tag.toLowerCase());
9194
+ if (normalizedUserTags.some((tag) => normalizedAllowedTags.includes(tag))) return true;
9195
+ const normalizedKeys = entitlementKeys.map(normalizeEntitlementKey);
9196
+ const normalizedAllowedEntitlements = (model.allowedEntitlements ?? []).map(normalizeEntitlementKey);
9197
+ return normalizedKeys.some((key) => normalizedAllowedEntitlements.includes(key));
9198
+ }
9199
+ /**
8886
9200
  * Telemetry Error Sanitizer
8887
9201
  *
8888
9202
  * Removes PII (Personally Identifiable Information) from error messages
@@ -9008,6 +9322,67 @@ z.array(triggerWordSchema).max(20, "Up to 20 trigger words allowed.").transform(
9008
9322
  }
9009
9323
  return out;
9010
9324
  });
9325
+ /**
9326
+ * Skill body argument substitution. Shared by the server-side SkillsFeature,
9327
+ * the LLM `skill` tool, and the CLI's skill executor so the same patterns
9328
+ * resolve identically across surfaces.
9329
+ *
9330
+ * Supports Claude Code's substitution syntax:
9331
+ * - `$ARGUMENTS` — all positional args joined by spaces
9332
+ * - `$1`, `$2`, ... — individual positional args
9333
+ *
9334
+ * Why a shared helper: the CLI already implemented this in
9335
+ * `apps/cli/src/utils/argumentSubstitution.ts`. Centralizing here avoids a
9336
+ * second drift surface — a skill body authored on the web and run in the CLI
9337
+ * (or vice versa) must expand identically.
9338
+ */
9339
+ function substituteArguments(template, args) {
9340
+ let result = template;
9341
+ for (let i = args.length; i >= 1; i--) {
9342
+ const pattern = new RegExp(`\\$${i}`, "g");
9343
+ result = result.replace(pattern, args[i - 1] ?? "");
9344
+ }
9345
+ return result.replace(/\$ARGUMENTS/g, args.join(" "));
9346
+ }
9347
+ /**
9348
+ * Shell-style argument splitter — supports double / single quotes for
9349
+ * grouping. Identical semantics to the CLI's `parseArguments`.
9350
+ *
9351
+ * parseSkillArguments('hello world') => ['hello', 'world']
9352
+ * parseSkillArguments('"hello world" test') => ['hello world', 'test']
9353
+ * parseSkillArguments("'one two' three") => ['one two', 'three']
9354
+ * parseSkillArguments('') => []
9355
+ *
9356
+ * Unclosed quotes are forgiven, not flagged: `parseSkillArguments('"unclosed')`
9357
+ * returns `['unclosed']`. This matches the CLI's pre-existing behavior and is
9358
+ * deliberate — skills are user-authored prose, not a shell, and surfacing a
9359
+ * parse error for a stray `"` would punish a typo more than the cost of a
9360
+ * slightly-wrong split warrants.
9361
+ */
9362
+ function parseSkillArguments(input) {
9363
+ if (!input) return [];
9364
+ const args = [];
9365
+ let current = "";
9366
+ let inQuotes = false;
9367
+ let quoteChar = "";
9368
+ for (let i = 0; i < input.length; i++) {
9369
+ const char = input[i];
9370
+ if (!inQuotes && (char === "\"" || char === "'")) {
9371
+ inQuotes = true;
9372
+ quoteChar = char;
9373
+ } else if (inQuotes && char === quoteChar) {
9374
+ inQuotes = false;
9375
+ quoteChar = "";
9376
+ } else if (!inQuotes && /\s/.test(char)) {
9377
+ if (current.length > 0) {
9378
+ args.push(current);
9379
+ current = "";
9380
+ }
9381
+ } else current += char;
9382
+ }
9383
+ if (current.length > 0) args.push(current);
9384
+ return args;
9385
+ }
9011
9386
  const VIEW_REGISTRY = [
9012
9387
  {
9013
9388
  id: "opti.root",
@@ -10364,6 +10739,14 @@ const CliConfigSchema = z.object({
10364
10739
  exportFormat: z.enum(["markdown", "json"]),
10365
10740
  maxIterations: z.number().nullable().prefault(10),
10366
10741
  enableSkillTool: z.boolean().optional().prefault(true),
10742
+ /**
10743
+ * When false (or set via the `--no-remote-skills` CLI flag / the
10744
+ * `B4M_NO_REMOTE_SKILLS=1` env var), the CLI skips fetching skills from
10745
+ * the B4M backend's `/api/skills` endpoint and runs with local files only.
10746
+ * Defaults to true so authenticated users get cross-machine skill sync
10747
+ * out of the box.
10748
+ */
10749
+ enableRemoteSkills: z.boolean().optional().prefault(true),
10367
10750
  enableDynamicAgentCreation: z.boolean().optional().prefault(false),
10368
10751
  enableCoordinatorMode: z.boolean().optional().prefault(false),
10369
10752
  /**
@@ -10455,6 +10838,7 @@ const DEFAULT_CONFIG = {
10455
10838
  exportFormat: "markdown",
10456
10839
  maxIterations: 10,
10457
10840
  enableSkillTool: true,
10841
+ enableRemoteSkills: true,
10458
10842
  enableDynamicAgentCreation: false,
10459
10843
  enableCoordinatorMode: false,
10460
10844
  promptVariant: "current"
@@ -11121,4 +11505,4 @@ var ConfigStore = class {
11121
11505
  }
11122
11506
  };
11123
11507
  //#endregion
11124
- export { ProjectEvents as $, GenerateImageToolCallSchema as A, dayjsConfig_default as At, InviteEvents as B, resolveNavigationIntents as Bt, ElabsEvents as C, UnauthorizedError as Ct, ForbiddenError as D, VideoModels as Dt, FileEvents as E, VideoGenerationUsageTransaction as Et, ImageEditUsageTransaction as F, isGPTImage2Model as Ft, ModalEvents as G, validateNotebookPath as Gt, KnowledgeType as H, secureParameters as Ht, ImageGenerationUsageTransaction as I, isGPTImageModel as It, OpenAIEmbeddingModel as J, isNearLimit as Jt, ModelBackend as K, buildRateLimitLogEntry as Kt, ImageModels as L, isSupportedEmbeddingModel as Lt, GenericCreditDeductTransaction as M, getDataLakeTags as Mt, HTTPError as N, getMcpProviderMetadata as Nt, FriendshipEvents as O, XAI_IMAGE_MODELS as Ot, HttpStatus as P, getViewById as Pt, ProfileEvents as Q, InboxEvents as R, isZodError as Rt, DashboardParamsSchema as S, UiNavigationEvents as St, FeedbackEvents as T, VIDEO_SIZE_CONSTRAINTS as Tt, LLMEvents as U, settingsMap as Ut, InviteType as V, sanitizeTelemetryError as Vt, MiscEvents as W, validateJupyterKernelName as Wt, Permission as X, CollectionType as Xt, OpenAIImageGenerationInput as Y, parseRateLimitHeaders as Yt, PermissionDeniedError as Z, ChatCompletionCreateInputSchema as _, TaskScheduleHandler as _t, ALERT_THRESHOLDS as a, ReceivedCreditTransaction as at, CompletionApiUsageTransaction as b, ToolUsageTransaction as bt, ApiKeyScope as c, ResearchModeParamsSchema as ct, ArtifactTypeSchema as d, ResearchTaskType as dt, PromptIntentSchema as et, AuthEvents as f, SessionEvents as ft, CREDIT_DEDUCT_TRANSACTION_TYPES as g, TagType as gt, BadRequestError as h, SupportedFabFileMimeTypes as ht, getEnvironmentName as i, RealtimeVoiceUsageTransaction as it, GenericCreditAddTransaction as j, getAccessibleDataLakes as jt, GEMINI_IMAGE_MODELS as k, b4mLLMTools as kt, ApiKeyType as l, ResearchTaskExecutionType as lt, BFL_SAFETY_TOLERANCE as m, SubscriptionCreditTransaction as mt, logger as n, PurchaseTransaction as nt, AiEvents as o, RechartsChartTypeList as ot, BFL_IMAGE_MODELS as p, SpeechToTextUsageTransaction as pt, NotFoundError as q, extractSnippetMeta as qt, getApiUrl as r, QuestMasterParamsSchema as rt, ApiKeyEvents as s, RegInviteEvents as st, ConfigStore as t, PromptMetaZodSchema as tt, AppFileEvents as u, ResearchTaskPeriodicFrequencyType as ut, ChatModels as v, TextGenerationUsageTransaction as vt, FavoriteDocumentType as w, UnprocessableEntityError as wt, CorruptedFileError as x, TransferCreditTransaction as xt, ClaudeArtifactMimeTypes as y, TooManyRequestsError as yt, InternalServerError as z, obfuscateApiKey as zt };
11508
+ export { ProjectEvents as $, parseRateLimitHeaders as $t, GenerateImageToolCallSchema as A, dayjsConfig_default as At, InviteEvents as B, obfuscateApiKey as Bt, ElabsEvents as C, UnauthorizedError as Ct, ForbiddenError as D, VideoModels as Dt, FileEvents as E, VideoGenerationUsageTransaction as Et, ImageEditUsageTransaction as F, isGPTImageModel as Ft, ModalEvents as G, settingsMap as Gt, KnowledgeType as H, resolveNavigationIntents as Ht, ImageGenerationUsageTransaction as I, isModelAccessible as It, OpenAIEmbeddingModel as J, validateJupyterKernelName as Jt, ModelBackend as K, substituteArguments as Kt, ImageModels as L, isSupportedEmbeddingModel as Lt, GenericCreditDeductTransaction as M, getMcpProviderMetadata as Mt, HTTPError as N, getViewById as Nt, FriendshipEvents as O, XAI_IMAGE_MODELS as Ot, HttpStatus as P, isGPTImage2Model as Pt, ProfileEvents as Q, isNearLimit as Qt, InboxEvents as R, isZodError as Rt, DashboardParamsSchema as S, UiNavigationEvents as St, FeedbackEvents as T, VIDEO_SIZE_CONSTRAINTS as Tt, LLMEvents as U, sanitizeTelemetryError as Ut, InviteType as V, parseSkillArguments as Vt, MiscEvents as W, secureParameters as Wt, Permission as X, buildRateLimitLogEntry as Xt, OpenAIImageGenerationInput as Y, validateNotebookPath as Yt, PermissionDeniedError as Z, extractSnippetMeta as Zt, ChatCompletionCreateInputSchema as _, TaskScheduleHandler as _t, ALERT_THRESHOLDS as a, ReceivedCreditTransaction as at, CompletionApiUsageTransaction as b, ToolUsageTransaction as bt, ApiKeyScope as c, ResearchModeParamsSchema as ct, ArtifactTypeSchema as d, ResearchTaskType as dt, CollectionType as en, PromptIntentSchema as et, AuthEvents as f, SessionEvents as ft, CREDIT_DEDUCT_TRANSACTION_TYPES as g, TagType as gt, BadRequestError as h, SupportedFabFileMimeTypes as ht, getEnvironmentName as i, RealtimeVoiceUsageTransaction as it, GenericCreditAddTransaction as j, getAccessibleDataLakes as jt, GEMINI_IMAGE_MODELS as k, b4mLLMTools as kt, ApiKeyType as l, ResearchTaskExecutionType as lt, BFL_SAFETY_TOLERANCE as m, SubscriptionCreditTransaction as mt, logger as n, PurchaseTransaction as nt, AiEvents as o, RechartsChartTypeList as ot, BFL_IMAGE_MODELS as p, SpeechToTextUsageTransaction as pt, NotFoundError as q, toDataLakeConfig as qt, getApiUrl as r, QuestMasterParamsSchema as rt, ApiKeyEvents as s, RegInviteEvents as st, ConfigStore as t, PromptMetaZodSchema as tt, AppFileEvents as u, ResearchTaskPeriodicFrequencyType as ut, ChatModels as v, TextGenerationUsageTransaction as vt, FavoriteDocumentType as w, UnprocessableEntityError as wt, CorruptedFileError as x, TransferCreditTransaction as xt, ClaudeArtifactMimeTypes as y, TooManyRequestsError as yt, InternalServerError as z, mapMimeTypeToArtifactType as zt };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-gV8aHo9b.mjs";
3
3
  //#region src/commands/apiCommand.ts
4
4
  /**
5
5
  * External API config command (--api-url / --reset-api)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "../package-3pouvthv.mjs";
2
+ import { t as version } from "../package-Dzetdo6D.mjs";
3
3
  import { a as fetchLatestVersion, c as isNpmPrefixWritable, i as compareSemver } from "../updateChecker-C8xsNY2L.mjs";
4
4
  import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
5
5
  import { execSync } from "child_process";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-gV8aHo9b.mjs";
3
3
  //#region src/commands/envCommand.ts
4
4
  /**
5
5
  * Environment switching for the `--dev` / `--prod` launch flags.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { $ as SessionStore, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, J as isReadOnlyTool, K as buildSystemPrompt, N as loadContextFiles, P as PermissionManager, S as ApiClient, T as FallbackLlmBackend, W as setWebSocketToolExecutor, X as CustomCommandStore, Y as ReActAgent, Z as CheckpointStore, _ as createAgentDelegateTool, b as createSkillTool, d as createFindDefinitionTool, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, k as McpManager, m as createCoordinateTaskTool, p as createWriteTodosTool, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-D9eSRR7Q.mjs";
3
- import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
2
+ import { C as createCoordinateTaskTool, D as FallbackLlmBackend, E as ApiClient, F as generateCliTools, G as ReActAgent, N as loadContextFiles, O as ServerLlmBackend, P as PermissionManager, Q as CheckpointStore, T as createAgentDelegateTool, W as setWebSocketToolExecutor, X as RemoteSkillSource, Y as isReadOnlyTool, Z as CustomCommandStore, _ as createGetFileStructureTool, a as WebSocketToolExecutor, b as createWriteTodosTool, et as SessionStore, i as McpManager, n as SubagentOrchestrator, o as WebSocketConnectionManager, q as buildSystemPrompt, r as AgentStore, s as WebSocketLlmBackend, t as BackgroundAgentManager, v as createFindDefinitionTool, w as createBackgroundAgentTools, x as createSkillTool, y as createTodoStore } from "../BackgroundAgentManager-C5HEirFN.mjs";
3
+ import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-gV8aHo9b.mjs";
4
4
  import { t as DEFAULT_SANDBOX_CONFIG } from "../types-LyRNHOiS.mjs";
5
5
  import { t as createSandboxRuntime } from "../SandboxRuntimeAdapter-ChGlxSGQ.mjs";
6
6
  import { t as SandboxOrchestrator } from "../SandboxOrchestrator-BoINxbX4.mjs";
@@ -62,6 +62,10 @@ async function handleHeadlessCommand(options) {
62
62
  process.exit(1);
63
63
  }
64
64
  const apiClient = new ApiClient(getApiUrl(config.apiConfig), configStore);
65
+ if (process.env.B4M_NO_REMOTE_SKILLS !== "1" && config.preferences.enableRemoteSkills !== false) try {
66
+ customCommandStore.setRemoteSource(new RemoteSkillSource(apiClient));
67
+ await customCommandStore.mergeRemoteCommands();
68
+ } catch {}
65
69
  const tokenGetter = async () => {
66
70
  return (await configStore.getAuthTokens())?.accessToken ?? null;
67
71
  };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-gV8aHo9b.mjs";
3
3
  //#region src/commands/mcpCommand.ts
4
4
  /**
5
5
  * External MCP commands (b4m mcp list, b4m mcp add, etc.)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "../package-3pouvthv.mjs";
2
+ import { t as version } from "../package-Dzetdo6D.mjs";
3
3
  import { c as isNpmPrefixWritable, l as setAutoUpdatePreference, n as REEXEC_GUARD_ENV, o as forceCheckForUpdate, r as checkForUpdate, s as getAutoUpdatePreference, t as INSTALL_CMD, u as shouldAttemptAutoUpdate } from "../updateChecker-C8xsNY2L.mjs";
4
4
  import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
5
5
  import { execSync, spawnSync } from "child_process";