sticky-note-cli 3.1.40 → 3.1.41

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/bin/cli.js CHANGED
@@ -720,12 +720,22 @@ async function cmdInit() {
720
720
  // Teammate flow: URL already in committed config — just need their API key
721
721
  print(`\n Cloud backend detected: ${configStickyUrl}`);
722
722
  const keyAnswer = await ask(rl, "Enter your STICKY_API_KEY (get it from your team lead)", "");
723
- if (keyAnswer.trim()) {
723
+ // GitHub push token — needed so session-end can push the data branch from
724
+ // background hook processes that don't have keychain/credential-manager access.
725
+ print("\n A GitHub Personal Access Token (PAT) is needed so sticky-note can push");
726
+ print(" thread data in the background without requiring interactive credential access.");
727
+ print(" Create one at https://github.com/settings/tokens with 'repo' scope (or");
728
+ print(" 'contents: write' for fine-grained tokens). Your team lead may share one.");
729
+ const pushTokenAnswer = await ask(rl, "Enter your STICKY_PUSH_TOKEN (GitHub PAT)", "");
730
+ if (keyAnswer.trim() || pushTokenAnswer.trim()) {
724
731
  const envPath = path.join(process.cwd(), ".env.sticky");
725
- fs.writeFileSync(envPath, `STICKY_URL=${configStickyUrl}\nSTICKY_API_KEY=${keyAnswer.trim()}\n`, "utf-8");
732
+ let envContent = `STICKY_URL=${configStickyUrl}\n`;
733
+ if (keyAnswer.trim()) envContent += `STICKY_API_KEY=${keyAnswer.trim()}\n`;
734
+ if (pushTokenAnswer.trim()) envContent += `STICKY_PUSH_TOKEN=${pushTokenAnswer.trim()}\n`;
735
+ fs.writeFileSync(envPath, envContent, "utf-8");
726
736
  print(" [OK] .env.sticky written");
727
737
  } else {
728
- print(" ⏭️ No API key entered — skipping .env.sticky (local-only mode)");
738
+ print(" ⏭️ No keys entered — skipping .env.sticky (local-only mode)");
729
739
  }
730
740
  } else {
731
741
  const cloudAnswer = await ask(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sticky-note-cli",
3
- "version": "3.1.40",
3
+ "version": "3.1.41",
4
4
  "description": "Human-to-human handoff for AI coding assistants. Git-backed shared memory for Claude Code and Copilot CLI.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -19,6 +19,52 @@ const DATA_REF = "refs/heads/" + DATA_BRANCH;
19
19
 
20
20
  const GIT_OPTS = { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] };
21
21
 
22
+ // ── Credential helpers ────────────────────────────────────
23
+
24
+ /**
25
+ * Read STICKY_PUSH_TOKEN from env or .env.sticky file.
26
+ * Returns the token string or null.
27
+ */
28
+ function _getPushToken() {
29
+ if (process.env.STICKY_PUSH_TOKEN) return process.env.STICKY_PUSH_TOKEN;
30
+ try {
31
+ // Walk up to find .env.sticky
32
+ let dir = process.cwd();
33
+ for (let i = 0; i < 20; i++) {
34
+ const envPath = path.join(dir, ".env.sticky");
35
+ if (fs.existsSync(envPath)) {
36
+ const content = fs.readFileSync(envPath, "utf-8");
37
+ const match = content.match(/^STICKY_PUSH_TOKEN=(.+)$/m);
38
+ if (match) return match[1].trim();
39
+ break;
40
+ }
41
+ const parent = path.dirname(dir);
42
+ if (parent === dir) break;
43
+ dir = parent;
44
+ }
45
+ } catch (_) {}
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Given a remote name, return an authenticated push URL if STICKY_PUSH_TOKEN
51
+ * is configured. Embeds the token into the HTTPS URL so the push works from
52
+ * any process context without needing a credential helper.
53
+ * Returns null if no token or remote URL is not HTTPS.
54
+ */
55
+ function _getAuthenticatedUrl(remote) {
56
+ const token = _getPushToken();
57
+ if (!token) return null;
58
+ try {
59
+ const url = execFileSync("git", ["remote", "get-url", remote], GIT_OPTS).trim();
60
+ if (!url.startsWith("https://")) return null;
61
+ // Insert token: https://TOKEN@github.com/...
62
+ return url.replace(/^https:\/\/([^@]*)@?/, `https://${token}@`);
63
+ } catch (_) {
64
+ return null;
65
+ }
66
+ }
67
+
22
68
  // ── Git helpers ───────────────────────────────────────────
23
69
 
24
70
  function getDefaultRemote() {
@@ -164,10 +210,13 @@ function pushDataBranch(remote, branchName, maxRetries, localMemPath, loadJsonFn
164
210
  if (!remote) return { ok: false, error: "no remote configured" };
165
211
 
166
212
  const pushSpec = DATA_REF + ":" + DATA_REF;
213
+ // Use token-authenticated URL if available — bypasses credential helpers so
214
+ // pushes work from background hook processes (no keychain/credential-manager needed).
215
+ const pushTarget = _getAuthenticatedUrl(remote) || remote;
167
216
 
168
217
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
169
218
  try {
170
- execFileSync("git", ["push", remote, pushSpec], {
219
+ execFileSync("git", ["push", pushTarget, pushSpec], {
171
220
  timeout: 30000, stdio: ["pipe", "pipe", "pipe"],
172
221
  });
173
222
  return { ok: true, error: null };
@@ -641,6 +641,15 @@ function appendAuditLineBoth(entry, cloud) {
641
641
  // ── Environment helpers ───────────────────────────────────
642
642
 
643
643
  function getUser() {
644
+ // Prefer git config user.name — consistent across machines regardless of OS login name.
645
+ // Fall back to OS env vars, then "unknown".
646
+ try {
647
+ const { execFileSync } = require("child_process");
648
+ const name = execFileSync("git", ["config", "user.name"], {
649
+ encoding: "utf-8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"],
650
+ }).trim();
651
+ if (name) return name;
652
+ } catch (_) {}
644
653
  return process.env.USER || process.env.USERNAME || "unknown";
645
654
  }
646
655