paperclip-github-plugin 0.9.8 → 0.9.10

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.
package/README.md CHANGED
@@ -19,7 +19,7 @@ With this plugin, you can:
19
19
  - import open GitHub issues into Paperclip without adding title prefixes or duplicate issues
20
20
  - keep descriptions, labels, and status aligned with GitHub over time
21
21
  - configure mappings and import defaults per Paperclip company
22
- - on authenticated Paperclip deployments, choose exactly which company agents should receive the saved GitHub token as `GITHUB_TOKEN`
22
+ - choose exactly which company agents should receive the saved GitHub token as `GITHUB_TOKEN`
23
23
  - run sync manually or on a schedule
24
24
  - triage open pull requests from mapped Paperclip projects in a hosted queue
25
25
  - give Paperclip agents native GitHub tools for issues, pull requests, CI, review threads, and org-level projects
@@ -29,7 +29,7 @@ With this plugin, you can:
29
29
  The plugin adds a full in-host workflow instead of a one-off import script:
30
30
 
31
31
  - a hosted settings page for GitHub auth, repository mappings, company defaults, execution-policy handoff fallbacks, and sync controls
32
- - authenticated-only setup controls for Paperclip board access and company-scoped agent token propagation
32
+ - setup controls for Paperclip board access and company-scoped agent token propagation
33
33
  - a dashboard widget that shows sync readiness, current sync status, and run/cancel controls
34
34
  - a separate KPI dashboard widget that tracks GitHub backlog size, GitHub issues closed, and Paperclip pull requests created with recent history and historical comparisons
35
35
  - saved sync diagnostics that let operators inspect the latest per-issue failures, raw errors, and suggested next steps
@@ -44,7 +44,7 @@ The plugin adds a full in-host workflow instead of a one-off import script:
44
44
  2. Connect one or more GitHub repositories to Paperclip projects.
45
45
  3. Run a sync manually or let the scheduled job keep things up to date.
46
46
 
47
- During sync, the plugin imports one top-level Paperclip issue per GitHub issue, stamps it with a namespaced GitHub Sync plugin origin, updates already imported issues instead of recreating them, maps GitHub labels into Paperclip labels, and keeps GitHub-specific metadata in dedicated Paperclip surfaces rather than stuffing everything into the issue description. On Paperclip `2026.512.0` and newer, the plugin passes the intended initial import status explicitly so Paperclip's assigned-issue creation defaults cannot override GitHub Sync's routing, and the detail surfaces can recover GitHub issue and pull request links from Paperclip's own `originKind` / `originId` fields when the plugin registry or legacy hidden marker is missing.
47
+ During sync, the plugin imports one top-level Paperclip issue per GitHub issue, stamps it with a namespaced GitHub Sync plugin origin, updates already imported issues instead of recreating them, maps GitHub labels into Paperclip labels, and keeps GitHub-specific metadata in dedicated Paperclip surfaces rather than stuffing everything into the issue description. On Paperclip `2026.517.0` and newer, the plugin passes the intended initial import status explicitly so Paperclip's assigned-issue creation defaults cannot override GitHub Sync's routing, and the detail surfaces can recover GitHub issue and pull request links from Paperclip's own `originKind` / `originId` fields when the plugin registry or legacy hidden marker is missing.
48
48
 
49
49
  When the host exposes plugin issue creation, imported GitHub issues are created through the Paperclip plugin SDK path so they are not attributed to the connected board user. The worker still uses direct local Paperclip REST calls for label sync and for description, assignee, or status repair paths when those routes are available.
50
50
 
@@ -106,7 +106,7 @@ They can also link a Paperclip issue to a GitHub issue or pull request in any ac
106
106
  ## Requirements
107
107
 
108
108
  - Node.js 20+
109
- - a Paperclip host with plugin installation enabled. GitHub Sync is built and tested against Paperclip `2026.512.0`; the manifest relies on explicit capabilities instead of a strict host-version gate because current latest/development hosts can report `0.0.0` during plugin upgrade.
109
+ - a Paperclip host with plugin installation enabled. GitHub Sync is built and tested against Paperclip `2026.517.0`; the manifest relies on explicit capabilities instead of a strict host-version gate because current latest/development hosts can report `0.0.0` during plugin upgrade.
110
110
  - a GitHub token with API access to the repositories you want to sync
111
111
 
112
112
  ## Install from npm
@@ -137,8 +137,8 @@ npx paperclipai plugin install --local "$PWD"
137
137
 
138
138
  1. Open the plugin settings for **GitHub Sync** from inside the Paperclip company you want to configure.
139
139
  2. Paste a GitHub token, validate it, and save it.
140
- 3. If the deployment is authenticated, connect Paperclip board access from the same settings page and complete the approval flow.
141
- 4. If the deployment is authenticated, choose which agents in the current company should receive the saved GitHub token as `GITHUB_TOKEN`.
140
+ 3. If the deployment is authenticated or local trusted, connect Paperclip board access from the same settings page and complete the approval flow when host API calls need board credentials.
141
+ 4. Choose which agents in the current company should receive the saved GitHub token as `GITHUB_TOKEN`.
142
142
  5. Add one or more repository mappings for the current company.
143
143
  6. For each mapping, either choose an existing GitHub-linked Paperclip project or enter the project name that should receive synced issues.
144
144
  7. Optionally configure company-wide defaults for imported issues, including the default assignee, the default Paperclip status, executor/reviewer/approver handoff assignees for sync-driven transitions, and ignored GitHub usernames. When Paperclip board access is connected, each assignee dropdown also offers `Me` for the connected board user. `Automatic routing` means GitHub Sync follows the issue's Paperclip execution policy first and only uses the saved fallback when Paperclip does not expose the next reviewer, approver, or return assignee yet. Bot aliases such as `renovate[bot]` are matched when you save `renovate`.
@@ -197,10 +197,10 @@ The plugin is designed to avoid persisting raw credentials in plugin state.
197
197
  - GitHub tokens saved through the UI are stored as per-company Paperclip secret references.
198
198
  - Paperclip board access tokens are also stored as per-company secret references.
199
199
  - The settings UI also keeps lightweight non-secret identity labels for those saved connections, so later visits can still show who each company GitHub token and board access are connected as.
200
- - On authenticated deployments, any selected propagation agents receive `GITHUB_TOKEN` as an agent env secret-ref binding that points at the same saved GitHub token secret instead of a copied raw token.
200
+ - Any selected propagation agents receive `GITHUB_TOKEN` as an agent env secret-ref binding that points at the same saved GitHub token secret instead of a copied raw token.
201
201
  - The worker resolves those secret references at runtime instead of storing raw tokens in plugin state.
202
202
  - When the current Paperclip host rejects plugin secret refs, GitHub Sync keeps company-scoped worker-local compatibility copies for GitHub tokens and Paperclip board-access tokens in `${PAPERCLIP_HOME:-~/.paperclip}/plugins/github-sync/config.json`. Reconnect board access once after upgrading if sync still cannot authenticate Paperclip label or issue REST calls.
203
- - On authenticated Paperclip deployments, sync is blocked until the relevant company has connected Paperclip board access.
203
+ - On authenticated Paperclip deployments, sync is blocked until the relevant company has connected Paperclip board access. On local trusted deployments, board access setup remains visible so operators can configure it for host API paths that still require board credentials, but missing board access does not by itself block sync preflight.
204
204
  - KPI API route requests must include `Authorization: Bearer <PAPERCLIP_API_KEY>` from an agent run; the Paperclip host authenticates the token and supplies the agent company before the worker records any metric event.
205
205
 
206
206
  ### Optional worker-local token file
@@ -225,7 +225,7 @@ Notes:
225
225
  - The raw token is never persisted back into plugin state or plugin config.
226
226
  - A GitHub token secret saved through the settings UI is the primary source. If the current Paperclip host rejects plugin secret-ref resolution while company-scoped plugin config is unavailable, GitHub Sync stores the validated token in `githubTokensByCompanyId` as a worker-local compatibility fallback.
227
227
  - A Paperclip board access secret saved through the settings UI is also the primary source. If the host cannot resolve it for plugin workers, reconnecting board access stores the approved board token in `paperclipBoardApiTokensByCompanyId` as a worker-local compatibility fallback for direct Paperclip REST calls.
228
- - On authenticated deployments, selected agents receive `GITHUB_TOKEN` as a latest-version secret-ref env binding, and the settings UI patches agent adapter config with `replaceAdapterConfig: true` so newer Paperclip hosts persist the merged env map.
228
+ - Selected agents receive `GITHUB_TOKEN` as a latest-version secret-ref env binding, and the settings UI patches agent adapter config with `replaceAdapterConfig: true` so newer Paperclip hosts persist the merged env map.
229
229
 
230
230
  ### Worker-facing Paperclip API URL
231
231
 
@@ -366,8 +366,8 @@ Useful scripts:
366
366
 
367
367
  - `pnpm dev` watches the manifest, worker, and UI bundles and rebuilds them into `dist/`
368
368
  - `pnpm dev:ui` starts a local Paperclip plugin UI dev server from `dist/ui` on port `4177`
369
- - `pnpm test:e2e` builds the plugin, boots an isolated Paperclip `2026.512.0` instance, installs the plugin, and verifies the hosted settings page renders
370
- - `pnpm verify:manual` builds the plugin, boots a local-trusted Paperclip `2026.512.0` instance for manual inspection, seeds a `Dummy Company` with a mapped review project and a `CEO` agent on the Codex local adapter using model `gpt-5.4`, installs the plugin, and opens the company dashboard without seeding KPI history.
369
+ - `pnpm test:e2e` builds the plugin, boots an isolated Paperclip `2026.517.0` instance, installs the plugin, and verifies the hosted settings page renders
370
+ - `pnpm verify:manual` builds the plugin, boots a local-trusted Paperclip `2026.517.0` instance for manual inspection, seeds a `Dummy Company` with a mapped review project and a `CEO` agent on the Codex local adapter using model `gpt-5.4`, installs the plugin, and opens the company dashboard without seeding KPI history.
371
371
 
372
372
  For fast hosted UI iteration, run `pnpm dev` in one terminal and `pnpm dev:ui` in another.
373
373
 
package/dist/manifest.js CHANGED
@@ -642,7 +642,7 @@ var PULL_REQUEST_ASSET_API_ROUTE_URL_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_I
642
642
  var require2 = createRequire(import.meta.url);
643
643
  var packageJson = require2("../package.json");
644
644
  var SCHEDULE_TICK_CRON = "* * * * *";
645
- var MANIFEST_VERSION = "0.9.8"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
645
+ var MANIFEST_VERSION = "0.9.10"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
646
646
  var manifest = {
647
647
  id: GITHUB_SYNC_PLUGIN_ID,
648
648
  apiVersion: 1,
package/dist/ui/index.js CHANGED
@@ -22408,10 +22408,21 @@ function normalizePaperclipHealthResponse(value) {
22408
22408
  ...authReady !== void 0 ? { authReady } : {}
22409
22409
  };
22410
22410
  }
22411
- function requiresPaperclipBoardAccess(value) {
22412
- const health = normalizePaperclipHealthResponse(value);
22411
+ function requiresPaperclipBoardAccessForHealth(health) {
22413
22412
  return health?.deploymentMode?.toLowerCase() === "authenticated";
22414
22413
  }
22414
+ function shouldShowPaperclipBoardAccessSettingsForHealth(health) {
22415
+ const deploymentMode = health?.deploymentMode?.toLowerCase();
22416
+ return deploymentMode === "authenticated" || deploymentMode === "local_trusted";
22417
+ }
22418
+ function resolvePaperclipAuthControlsPolicy(value) {
22419
+ const health = normalizePaperclipHealthResponse(value);
22420
+ return {
22421
+ boardAccessRequired: requiresPaperclipBoardAccessForHealth(health),
22422
+ boardAccessSettingsVisible: shouldShowPaperclipBoardAccessSettingsForHealth(health),
22423
+ githubTokenPropagationSettingsVisible: true
22424
+ };
22425
+ }
22415
22426
 
22416
22427
  // src/ui/assignees.ts
22417
22428
  function normalizeCompanyAssigneeOptionsResponse(response) {
@@ -23098,7 +23109,12 @@ function getSyncSetupMessage(issue, hasCompanyContext) {
23098
23109
  }
23099
23110
  }
23100
23111
  function usePaperclipBoardAccessRequirement() {
23101
- const [status, setStatus] = useState2("loading");
23112
+ const [state, setState] = useState2({
23113
+ status: "loading",
23114
+ required: false,
23115
+ settingsVisible: false,
23116
+ githubTokenPropagationSettingsVisible: true
23117
+ });
23102
23118
  useEffect2(() => {
23103
23119
  let cancelled = false;
23104
23120
  void (async () => {
@@ -23107,19 +23123,28 @@ function usePaperclipBoardAccessRequirement() {
23107
23123
  return;
23108
23124
  }
23109
23125
  if (!health) {
23110
- setStatus("unknown");
23126
+ const policy2 = resolvePaperclipAuthControlsPolicy(null);
23127
+ setState({
23128
+ status: "unknown",
23129
+ required: policy2.boardAccessRequired,
23130
+ settingsVisible: policy2.boardAccessSettingsVisible,
23131
+ githubTokenPropagationSettingsVisible: policy2.githubTokenPropagationSettingsVisible
23132
+ });
23111
23133
  return;
23112
23134
  }
23113
- setStatus(requiresPaperclipBoardAccess(health) ? "required" : "not_required");
23135
+ const policy = resolvePaperclipAuthControlsPolicy(health);
23136
+ setState({
23137
+ status: policy.boardAccessRequired ? "required" : "not_required",
23138
+ required: policy.boardAccessRequired,
23139
+ settingsVisible: policy.boardAccessSettingsVisible,
23140
+ githubTokenPropagationSettingsVisible: policy.githubTokenPropagationSettingsVisible
23141
+ });
23114
23142
  })();
23115
23143
  return () => {
23116
23144
  cancelled = true;
23117
23145
  };
23118
23146
  }, []);
23119
- return {
23120
- status,
23121
- required: status === "required"
23122
- };
23147
+ return state;
23123
23148
  }
23124
23149
  function getGitHubRateLimitResourceLabel(resource) {
23125
23150
  switch (resource?.trim().toLowerCase()) {
@@ -32384,6 +32409,8 @@ function GitHubSyncSettingsPage() {
32384
32409
  const hasSavedToken = Boolean(form.githubTokenConfigured || showSavedTokenHint);
32385
32410
  const boardAccessConfigured = Boolean(form.paperclipBoardAccessConfigured);
32386
32411
  const boardAccessRequired = boardAccessRequirement.required;
32412
+ const boardAccessSettingsVisible = boardAccessRequirement.settingsVisible;
32413
+ const githubTokenPropagationSettingsVisible = boardAccessRequirement.githubTokenPropagationSettingsVisible;
32387
32414
  const boardAccessReady = !boardAccessRequired || hasCompanyContext && boardAccessConfigured;
32388
32415
  const tokenStatus = tokenStatusOverride ?? (hasSavedToken ? "valid" : "required");
32389
32416
  const tokenTone = tokenStatus === "valid" ? "success" : tokenStatus === "invalid" ? "danger" : "warning";
@@ -32492,7 +32519,7 @@ function GitHubSyncSettingsPage() {
32492
32519
  const manualSyncScopePillLabel = hasCompanyContext ? "This company" : "All companies";
32493
32520
  const manualSyncButtonLabel = hasCompanyContext ? "Run sync for this company" : "Run sync across all companies";
32494
32521
  const advancedSettingsSummary = formatAdvancedSettingsSummary(form.advancedSettings, availableAssignees, {
32495
- includePropagation: boardAccessRequired
32522
+ includePropagation: githubTokenPropagationSettingsVisible
32496
32523
  });
32497
32524
  const assigneeSelectOptions = [
32498
32525
  { value: "", label: "Unassigned" },
@@ -32646,9 +32673,6 @@ function GitHubSyncSettingsPage() {
32646
32673
  });
32647
32674
  }
32648
32675
  async function propagateGitHubTokenToSelectedAgents(options) {
32649
- if (!boardAccessRequired) {
32650
- return;
32651
- }
32652
32676
  const selectedAgentIds = normalizeAgentIds(options.selectedAgentIds);
32653
32677
  const previousAgentIds = normalizeAgentIds(options.previousAgentIds);
32654
32678
  if (selectedAgentIds.length === 0 && previousAgentIds.length === 0) {
@@ -32855,7 +32879,7 @@ function GitHubSyncSettingsPage() {
32855
32879
  }));
32856
32880
  toast({
32857
32881
  title: boardIdentity.label ? `Paperclip board access connected as ${boardIdentity.label}` : "Paperclip board access connected",
32858
- body: "Direct Paperclip REST calls can now authenticate in authenticated deployments.",
32882
+ body: "Direct Paperclip REST calls can now authenticate when the host requires board access.",
32859
32883
  tone: "success"
32860
32884
  });
32861
32885
  notifyGitHubSyncSettingsChanged();
@@ -32928,7 +32952,7 @@ function GitHubSyncSettingsPage() {
32928
32952
  if (!trustedPaperclipApiBaseUrl) {
32929
32953
  throw new Error("Could not resolve the current browser origin for Paperclip API calls.");
32930
32954
  }
32931
- const nextConfiguredPaperclipApiBaseUrl = paperclipApiBaseUrlIsOverride ? normalizedPaperclipApiBaseUrlDraft ?? "" : "";
32955
+ const nextConfiguredPaperclipApiBaseUrl = trustedPaperclipApiBaseUrl;
32932
32956
  await patchPluginConfig(pluginId, {
32933
32957
  paperclipApiBaseUrl: nextConfiguredPaperclipApiBaseUrl
32934
32958
  });
@@ -32963,11 +32987,11 @@ function GitHubSyncSettingsPage() {
32963
32987
  advancedSettings: normalizeAdvancedSettings(result.advancedSettings),
32964
32988
  availableAssignees: result.availableAssignees ?? current.availableAssignees,
32965
32989
  paperclipApiBaseUrl: result.paperclipApiBaseUrl,
32966
- paperclipApiBaseUrlConfigured: paperclipApiBaseUrlIsOverride,
32990
+ paperclipApiBaseUrlConfigured: Boolean(nextConfiguredPaperclipApiBaseUrl),
32967
32991
  updatedAt: result.updatedAt
32968
32992
  }));
32969
32993
  setScheduleFrequencyDraft(String(normalizeScheduleFrequencyMinutes(result.scheduleFrequencyMinutes)));
32970
- setPaperclipApiBaseUrlDraft(paperclipApiBaseUrlIsOverride ? nextConfiguredPaperclipApiBaseUrl : trustedPaperclipApiBaseUrl);
32994
+ setPaperclipApiBaseUrlDraft(nextConfiguredPaperclipApiBaseUrl);
32971
32995
  toast({
32972
32996
  title: "GitHub sync setup saved",
32973
32997
  body: `Advanced defaults, mappings, and automatic sync are saved for ${currentCompanyName}.`,
@@ -33224,7 +33248,7 @@ function GitHubSyncSettingsPage() {
33224
33248
  /* @__PURE__ */ jsx2("div", { className: "ghsync__permission-audit-list", children: /* @__PURE__ */ jsx2("div", { className: "ghsync__permission-audit-item", children: /* @__PURE__ */ jsx2("span", { children: tokenPermissionAuditData?.warnings[0] ?? "Add a mapped repository in this company so GitHub Sync can verify the token permissions it needs." }) }) })
33225
33249
  ] }) : null
33226
33250
  ] }),
33227
- boardAccessRequired ? /* @__PURE__ */ jsxs2("section", { className: "ghsync__section", children: [
33251
+ boardAccessSettingsVisible ? /* @__PURE__ */ jsxs2("section", { className: "ghsync__section", children: [
33228
33252
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__section-head", children: [
33229
33253
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__section-copy", children: [
33230
33254
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__section-title-row", children: [
@@ -33565,7 +33589,7 @@ function GitHubSyncSettingsPage() {
33565
33589
  ),
33566
33590
  /* @__PURE__ */ jsx2("p", { className: "ghsync__hint", children: "Comma or newline separated." })
33567
33591
  ] }),
33568
- boardAccessRequired ? /* @__PURE__ */ jsxs2("div", { className: "ghsync__field", children: [
33592
+ githubTokenPropagationSettingsVisible ? /* @__PURE__ */ jsxs2("div", { className: "ghsync__field", children: [
33569
33593
  /* @__PURE__ */ jsx2("label", { htmlFor: "advanced-token-propagation", children: "Propagate GitHub token to agents" }),
33570
33594
  /* @__PURE__ */ jsx2(
33571
33595
  SettingsAgentMultiPicker,
@@ -33769,7 +33793,7 @@ function GitHubSyncSettingsPage() {
33769
33793
  ] }),
33770
33794
  /* @__PURE__ */ jsx2("span", { children: !repositoriesUnlocked ? "Requires a token." : savedMappingCount > 0 ? hasCompanyContext ? `${savedMappingCount} saved.` : `${savedMappingCount} saved.` : hasCompanyContext ? "Add a repository." : "Select a company." })
33771
33795
  ] }),
33772
- boardAccessRequired ? /* @__PURE__ */ jsxs2("div", { className: "ghsync__check", children: [
33796
+ boardAccessSettingsVisible ? /* @__PURE__ */ jsxs2("div", { className: "ghsync__check", children: [
33773
33797
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__check-top", children: [
33774
33798
  /* @__PURE__ */ jsx2("strong", { children: "Paperclip board access" }),
33775
33799
  /* @__PURE__ */ jsx2("span", { className: `ghsync__badge ${getToneClass(boardAccessStatusTone)}`, children: boardAccessStatusLabel })