pipebrain-mcp 0.32.0 → 0.33.0

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 (2) hide show
  1. package/dist/main.js +117 -13
  2. package/package.json +2 -2
package/dist/main.js CHANGED
@@ -356,9 +356,52 @@ var require_main = __commonJS({
356
356
  }
357
357
  });
358
358
 
359
+ // ../../libs/agent-shared/dist/agent-config.js
360
+ import { existsSync, readFileSync } from "node:fs";
361
+ import { homedir } from "node:os";
362
+ import { join } from "node:path";
363
+
364
+ // ../../libs/agent-shared/dist/agent-config-manifest.js
365
+ var AGENT_CONFIG_DIGEST = "898da8356dac55bd14531dcb5440aa3bd7bbcaa72e873d0fee4a102e3d718ee1";
366
+
367
+ // ../../libs/agent-shared/dist/agent-config.js
368
+ function agentConfigDir(env = process.env) {
369
+ const configured = env.CLAUDE_CONFIG_DIR;
370
+ return configured !== void 0 && configured !== "" ? configured : join(homedir(), ".claude");
371
+ }
372
+ var STAMP_REL = "pipebrain/installed.json";
373
+ function readInstalledStamp(dir) {
374
+ const file2 = join(dir, STAMP_REL);
375
+ if (!existsSync(file2))
376
+ return null;
377
+ try {
378
+ const parsed = JSON.parse(readFileSync(file2, "utf8"));
379
+ if (typeof parsed !== "object" || parsed === null)
380
+ return null;
381
+ const stamp = parsed;
382
+ return typeof stamp.configDigest === "string" && stamp.configDigest !== "" ? {
383
+ configDigest: stamp.configDigest,
384
+ installedBy: typeof stamp.installedBy === "string" ? stamp.installedBy : "unknown",
385
+ installedAt: typeof stamp.installedAt === "string" ? stamp.installedAt : "unknown"
386
+ } : null;
387
+ } catch {
388
+ return null;
389
+ }
390
+ }
391
+ function agentConfigStatus(dir, shipped = AGENT_CONFIG_DIGEST) {
392
+ const stamp = readInstalledStamp(dir);
393
+ if (stamp === null)
394
+ return "absent";
395
+ return stamp.configDigest === shipped ? "current" : "behind";
396
+ }
397
+ var UPDATE_COMMAND = "ppb setup";
398
+ function agentConfigNotice(status) {
399
+ return status === "behind" ? `This machine's shared agent configuration is not the one this build ships \u2014 run \`${UPDATE_COMMAND}\` to see what would change and update it.` : null;
400
+ }
401
+
359
402
  // ../../libs/agent-shared/dist/asset-transfer.js
360
403
  import { createHash } from "node:crypto";
361
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
404
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
362
405
  import { basename, extname } from "node:path";
363
406
  var MIME_BY_EXT = {
364
407
  ".png": "image/png",
@@ -381,9 +424,9 @@ function guessContentType(filename) {
381
424
  return MIME_BY_EXT[extname(filename).toLowerCase()] ?? "application/octet-stream";
382
425
  }
383
426
  async function uploadAssetFromPath(client2, apiBaseUrl, input) {
384
- if (!existsSync(input.path))
427
+ if (!existsSync2(input.path))
385
428
  throw new Error(`file not found: ${input.path}`);
386
- const bytes = readFileSync(input.path);
429
+ const bytes = readFileSync2(input.path);
387
430
  const filename = basename(input.path);
388
431
  const contentType = input.contentType ?? guessContentType(filename);
389
432
  const sha256 = createHash("sha256").update(bytes).digest("hex");
@@ -15589,6 +15632,22 @@ var orgInstructionSchema = external_exports.object({
15589
15632
  body: external_exports.string().min(1),
15590
15633
  createdBy: uuidSchema.nullable(),
15591
15634
  viaTokenId: uuidSchema.nullable(),
15635
+ /**
15636
+ * sha256 of the shipped default this row was SEEDED from, or null when nothing seeded
15637
+ * it — a row written by a person, a row re-created after its scope was cleared, or a row
15638
+ * that predates seeding.
15639
+ *
15640
+ * It is what lets a settings page say the shipped default has MOVED since this
15641
+ * organization got it, without the product ever applying that move: comparing this digest
15642
+ * against the running release's default for the same scope is the whole signal. It is not
15643
+ * updated by an edit, because what it records is what was seeded — which stays true after
15644
+ * the organization rewrites the body, and is exactly then that the comparison is worth
15645
+ * making.
15646
+ *
15647
+ * `.default(null)` rather than a bare nullable, for the tolerance this schema keeps
15648
+ * throughout: a payload from a server that predates the column still parses.
15649
+ */
15650
+ seededDefaultSha: external_exports.string().nullable().default(null),
15592
15651
  createdAt: external_exports.iso.datetime({ offset: true }),
15593
15652
  updatedAt: external_exports.iso.datetime({ offset: true }),
15594
15653
  deletedAt: external_exports.iso.datetime({ offset: true }).nullable()
@@ -16105,6 +16164,12 @@ var listEnvelopeSchema = (item) => external_exports.object({ items: external_exp
16105
16164
  var mintedTokenSchema = apiTokenSchema.extend({ token: external_exports.string().min(1) }).loose();
16106
16165
  var tokenListSchema = listEnvelopeSchema(apiTokenSchema);
16107
16166
  var orgInstructionListSchema = listEnvelopeSchema(orgInstructionSchema);
16167
+ var instructionDefaultSchema = external_exports.object({
16168
+ scope: external_exports.string().min(1),
16169
+ body: external_exports.string().min(1),
16170
+ sha256: external_exports.string().min(1)
16171
+ }).loose();
16172
+ var instructionDefaultListSchema = listEnvelopeSchema(instructionDefaultSchema);
16108
16173
  var ApiClient = class _ApiClient {
16109
16174
  baseUrl;
16110
16175
  auth;
@@ -16715,6 +16780,18 @@ var ApiClient = class _ApiClient {
16715
16780
  const body = await this.request("GET", "/api/v1/org-instructions", {}, orgInstructionListSchema);
16716
16781
  return body.items;
16717
16782
  }
16783
+ /**
16784
+ * The starting text THIS RELEASE ships, per scope, with the digest a seeded row records.
16785
+ *
16786
+ * Shipped content rather than the org's, so it carries no tenant data and any
16787
+ * authenticated caller may read it. Comparing a row's `seededDefaultSha` against the
16788
+ * `sha256` here is how a settings page says the shipped default has MOVED since this
16789
+ * organization was seeded — a difference the product reports and never applies.
16790
+ */
16791
+ async listInstructionDefaults() {
16792
+ const body = await this.request("GET", "/api/v1/org-instructions/defaults", {}, instructionDefaultListSchema);
16793
+ return body.items;
16794
+ }
16718
16795
  /** Upserts one scope's instruction text (ADMIN+; body ≤ 4000 chars). */
16719
16796
  setOrgInstruction(scope, body) {
16720
16797
  return this.request("PUT", `/api/v1/org-instructions/${enc(scope)}`, { body: { body } }, orgInstructionSchema);
@@ -16867,11 +16944,18 @@ function bindingBanner(apiBaseUrl) {
16867
16944
  return `Pipebrain MCP server, bound to the ${apiBindingLabel(apiBaseUrl)} API at ${apiBaseUrl}. Every tool reads and writes THAT environment \u2014 confirm it is the one you intend before mutating.`;
16868
16945
  }
16869
16946
  var INDEX_RULES = "Until you have it: an open decision is filed as a question with `ask_questions` and never written into a body; search for an existing record before creating one, because a duplicate splits one thing\u2019s history across two identifiers and nothing merges them afterward; and a secret value never travels in a tool call, because a value in a call is disclosed before the server can encrypt it.";
16870
- function composeIndex(base) {
16947
+ function composeIndex(base, machineNotice2 = null) {
16871
16948
  return [
16872
16949
  base,
16873
16950
  "Call `get_work_context` before you write anything \u2014 it returns the binding guidance, the product doctrine and this organization\u2019s own writing rules, together with the work. Nothing else delivers that guidance in full; every other read only names this call.",
16874
- INDEX_RULES
16951
+ INDEX_RULES,
16952
+ // LAST, and usually absent. It is about the MACHINE rather than about the work, so it
16953
+ // must never displace a rule: a squeezed share keeps the prefix, and losing a sentence
16954
+ // about an installer costs less than losing the sentence that recovers everything else.
16955
+ // Present only when this machine's shared configuration is not the one this build ships
16956
+ // (PIPE-354 AC-5) — a machine that never installed it is passed over in silence, since
16957
+ // it has no configuration to be behind with.
16958
+ ...machineNotice2 === null ? [] : [machineNotice2]
16875
16959
  ].join("\n\n");
16876
16960
  }
16877
16961
  async function fetchOrgInstructionsSafe(client2) {
@@ -16917,6 +17001,13 @@ function successorNotice() {
16917
17001
  const armed = process.env.PIPELINE_MCP_SUCCESSOR_NOTICE;
16918
17002
  return armed === void 0 || armed === "" ? "" : SUCCESSOR_NOTICE;
16919
17003
  }
17004
+ function machineNotice() {
17005
+ try {
17006
+ return agentConfigNotice(agentConfigStatus(agentConfigDir()));
17007
+ } catch {
17008
+ return null;
17009
+ }
17010
+ }
16920
17011
  var DEFAULT_VERSION = "0.0.0";
16921
17012
  function ok(data, appended) {
16922
17013
  const content = [
@@ -16983,7 +17074,13 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
16983
17074
  // which environment this is bound to, the call that returns the binding guidance,
16984
17075
  // and the three rules whose violation cannot be undone. Everything else comes from
16985
17076
  // `docs/DOCTRINE.md` and the org's saved texts, on the work-context read.
16986
- instructions: composeIndex(successorNotice() + banner)
17077
+ //
17078
+ // Plus, only when there is something to say, one line about the MACHINE: its shared
17079
+ // agent configuration is not the one this build ships (PIPE-354 AC-5). Read here
17080
+ // rather than passed in, because the fact is about the filesystem this process is
17081
+ // running on and there is no caller better placed to know it; a read failure
17082
+ // degrades to silence, since a session must not fail to start over an installer.
17083
+ instructions: composeIndex(successorNotice() + banner, machineNotice())
16987
17084
  });
16988
17085
  server2.registerTool("list_projects", {
16989
17086
  description: "List all projects with their key, environments, milestones (token, label, active), and open-issue counts \u2014 the project switcher and workflow entry point (\xA77).",
@@ -17012,10 +17109,17 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17012
17109
  // path that has no precedent anywhere on this surface. The viewer narrowing the
17013
17110
  // web queue offers is filter state on that view, not something an agent picks.
17014
17111
  }, () => (
17015
- // `{ items }`, mirroring the HTTP envelope rather than unwrapping to a bare
17016
- // array like the sibling list tools: AC-7 asks that an agent read the SAME set
17017
- // the queue shows, and the two surfaces answering in the same shape is what
17018
- // makes that comparable at a glance.
17112
+ // `{ items }`, mirroring the HTTP envelope rather than unwrapping to a bare array
17113
+ // like the sibling list tools. The reason is the envelope itself: this tool is a
17114
+ // pass-through of one endpoint's answer, and reshaping it here would be a second
17115
+ // place for that answer's shape to be decided.
17116
+ //
17117
+ // The two surfaces are NOT interchangeable, and nothing here should suggest they
17118
+ // are. This tool takes no narrowing arguments and returns the org's WHOLE queue,
17119
+ // while the web view narrows what it DRAWS — by project, by category, and by viewer
17120
+ // scope. One endpoint decides what counts as a gate, so that definition cannot drift
17121
+ // between them; what an agent reads here is everything waiting, rather than whatever
17122
+ // the operator happens to be looking at.
17019
17123
  run(async () => ({ items: await client2.listWaiting() }), GUIDANCE_POINTER)
17020
17124
  ));
17021
17125
  server2.registerTool(
@@ -17352,11 +17456,11 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17352
17456
  inputSchema: linkAssetInputSchema.shape
17353
17457
  }, (args) => run(() => client2.linkAsset(args)));
17354
17458
  server2.registerTool("unlink_asset", {
17355
- description: "Remove an asset reference by its link id (soft-delete). The asset itself is untouched \u2014 this only drops the reference from its target.",
17459
+ description: "Remove an asset reference by its link id (soft-delete). The asset itself is untouched \u2014 this only drops the reference from its target. A link whose target is a CHAT MESSAGE is author-only: anyone but the person who wrote that message is refused with `not_message_author` (403), so an attachment you did not post is not yours to detach. Every other target type is removable by any member.",
17356
17460
  inputSchema: unlinkAssetInputSchema.shape
17357
17461
  }, (args) => run(() => client2.unlinkAsset(args.linkId)));
17358
17462
  server2.registerTool("set_secret", {
17359
- description: "Create a secret or add a new version under a project + environment (metadata only \u2014 the response NEVER echoes the value). `name` is UPPER_SNAKE_CASE (e.g. API_KEY); `environment` is the environment slug (dev/prod); optional `description`.",
17463
+ description: "Create a secret or add a new version under a project + environment (metadata only \u2014 the response NEVER echoes the value). `name` is UPPER_SNAKE_CASE (e.g. API_KEY); `environment` is the environment slug (dev/prod); optional `description`. Writing guidance for that description lives in the product doctrine, which `get_work_context` returns.",
17360
17464
  inputSchema: {
17361
17465
  project: projectKeySchema,
17362
17466
  environment: external_exports.string().min(1),
@@ -17485,7 +17589,7 @@ function loadMcpConfig() {
17485
17589
  }
17486
17590
 
17487
17591
  // src/version.ts
17488
- var VERSION = "0.32.0";
17592
+ var VERSION = "0.33.0";
17489
17593
 
17490
17594
  // src/main.ts
17491
17595
  var config2 = loadMcpConfig();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pipebrain-mcp",
3
- "version": "0.32.0",
4
- "description": "Pipebrain MCP server for Claude Code and Claude Desktop — issues, requirements, content, assets, and metadata-only secrets over the Pipebrain API.",
3
+ "version": "0.33.0",
4
+ "description": "Pipebrain MCP server for Claude Code and Claude Desktop \u2014 issues, requirements, content, assets, and metadata-only secrets over the Pipebrain API.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "main": "./dist/main.js",