sticky-note-cli 3.1.5 → 3.1.7

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
@@ -14,6 +14,7 @@
14
14
 
15
15
  const fs = require("fs");
16
16
  const path = require("path");
17
+ const os = require("os");
17
18
  const readline = require("readline");
18
19
  const { execSync, execFileSync } = require("child_process");
19
20
 
@@ -551,6 +552,11 @@ async function cmdInit() {
551
552
  print(" [OK] Git repository detected");
552
553
  print("");
553
554
 
555
+ // Read existing sticky-note-config.json for teammate flow (cloud URL pre-set via git)
556
+ const existingConfigPath = path.join(process.cwd(), ".sticky-note", "sticky-note-config.json");
557
+ const existingConfig = readJsonSafe(existingConfigPath, {});
558
+ const configStickyUrl = existingConfig.sticky_url || "";
559
+
554
560
  // Auto-detect MCP servers and skills
555
561
  print(" Scanning for existing configuration...");
556
562
  const detectedMcp = detectMcpServers();
@@ -575,6 +581,7 @@ async function cmdInit() {
575
581
 
576
582
  let mcpServers, skills, conventions, staleDaysResolved, injectTokenBudgetResolved;
577
583
  let wantsCloud = false;
584
+ let cloudProvisionedUrl = null; // set after successful provisioning, written to config
578
585
 
579
586
  if (ciMode) {
580
587
  // CI mode: no prompts, use env vars
@@ -597,7 +604,7 @@ async function cmdInit() {
597
604
  conventions = convRaw ? convRaw.split(",").map(s => s.trim()).filter(Boolean) : [];
598
605
  staleDaysResolved = parseIntOr(staleDaysRaw, 14);
599
606
  injectTokenBudgetResolved = 1000;
600
- wantsCloud = v3Mode; // --v3 in CI mode implies cloud
607
+ wantsCloud = v3Mode && !configStickyUrl; // --v3 implies cloud, but not if URL already in config
601
608
  } else {
602
609
  // Interactive prompts
603
610
  const rl = readline.createInterface({
@@ -625,12 +632,25 @@ async function cmdInit() {
625
632
  // Sync MCP servers from teammate's sticky-note config
626
633
  await syncMcpFromStickyNote(rl);
627
634
 
628
- const cloudAnswer = await ask(
629
- rl,
630
- "Set up Cloudflare cloud backend for real-time team sync? (y/N)",
631
- v3Mode ? "y" : "N"
632
- );
633
- wantsCloud = cloudAnswer.toLowerCase() === "y" || cloudAnswer.toLowerCase() === "yes";
635
+ if (configStickyUrl) {
636
+ // Teammate flow: URL already in committed config — just need their API key
637
+ print(`\n Cloud backend detected: ${configStickyUrl}`);
638
+ const keyAnswer = await ask(rl, "Enter your STICKY_API_KEY (get it from your team lead)", "");
639
+ if (keyAnswer.trim()) {
640
+ const envPath = path.join(process.cwd(), ".env.sticky");
641
+ fs.writeFileSync(envPath, `STICKY_URL=${configStickyUrl}\nSTICKY_API_KEY=${keyAnswer.trim()}\n`, "utf-8");
642
+ print(" [OK] .env.sticky written");
643
+ } else {
644
+ print(" ⏭️ No API key entered — skipping .env.sticky (local-only mode)");
645
+ }
646
+ } else {
647
+ const cloudAnswer = await ask(
648
+ rl,
649
+ "Set up Cloudflare cloud backend for real-time team sync? (y/N)",
650
+ v3Mode ? "y" : "N"
651
+ );
652
+ wantsCloud = cloudAnswer.toLowerCase() === "y" || cloudAnswer.toLowerCase() === "yes";
653
+ }
634
654
 
635
655
  rl.close();
636
656
 
@@ -707,13 +727,13 @@ async function cmdInit() {
707
727
  print(" [WARN] Cloud setup failed: " + cloudResult.error);
708
728
  print(" Run `npx sticky-note deploy-backend` to retry.\n");
709
729
  } else if (cloudResult && !cloudResult.error) {
710
- if (cloudResult.secretSet) {
711
- print("\n Share STICKY_URL and STICKY_API_KEY with teammates securely.");
712
- print(" (e.g. org secrets, 1Password — do not commit .env.sticky)\n");
713
- } else {
714
- print("\n [!] API key written to .env.sticky but NOT yet set on the Worker.");
715
- print(" Run the manual command shown above, then share with your team.\n");
716
- }
730
+ cloudProvisionedUrl = cloudResult.workerUrl;
731
+ print("\n ┌─────────────────────────────────────────────────────────────┐");
732
+ print(" │ Share this with teammates (once, via DM or 1Password): │");
733
+ print(` │ STICKY_API_KEY=${cloudResult.apiKey.padEnd(43)}│`);
734
+ print(" │ │");
735
+ print(" │ STICKY_URL is committed to git — teammates get it on pull. │");
736
+ print(" └─────────────────────────────────────────────────────────────┘\n");
717
737
  }
718
738
  } else if (wantsCloud && ciMode) {
719
739
  // CI mode: can't pause for wrangler install, just try and warn on failure
@@ -845,6 +865,7 @@ async function cmdInit() {
845
865
  existing.inject_token_budget = injectTokenBudgetResolved;
846
866
  existing.hook_version = VERSION;
847
867
  existing.min_version = bumpMinVersion(existing.min_version, VERSION);
868
+ if (cloudProvisionedUrl) existing.sticky_url = cloudProvisionedUrl;
848
869
  fs.writeFileSync(configDest, JSON.stringify(existing, null, 2) + "\n");
849
870
  }
850
871
  print(" [OK] .sticky-note/sticky-note-config.json");
@@ -1072,16 +1093,43 @@ async function cmdInit() {
1072
1093
  }
1073
1094
  }
1074
1095
 
1096
+ // Register sticky-note MCP server in Claude Code's global settings (~/.claude/settings.json)
1097
+ const claudeGlobalSettingsPath = path.join(os.homedir(), ".claude", "settings.json");
1098
+ try {
1099
+ const claudeGlobal = readJsonSafe(claudeGlobalSettingsPath, {});
1100
+ if (!claudeGlobal.mcpServers) claudeGlobal.mcpServers = {};
1101
+ if (!claudeGlobal.mcpServers["sticky-note"]) {
1102
+ claudeGlobal.mcpServers["sticky-note"] = {
1103
+ type: "stdio",
1104
+ command: "npx",
1105
+ args: ["-y", "-p", "sticky-note-cli", "sticky-note", "mcp-server"],
1106
+ };
1107
+ fs.writeFileSync(claudeGlobalSettingsPath, JSON.stringify(claudeGlobal, null, 2) + "\n");
1108
+ print(" [OK] Registered sticky-note MCP server in ~/.claude/settings.json (Claude Code)");
1109
+ } else {
1110
+ print(" ⏭️ sticky-note already registered in ~/.claude/settings.json");
1111
+ }
1112
+ } catch (err) {
1113
+ print(" ⏭️ Could not update ~/.claude/settings.json: " + (err.message || err));
1114
+ }
1115
+
1075
1116
  // Done!
1076
- print("\n ✨ Sticky Note V2 initialized!\n");
1117
+ print("\n ✨ Sticky Note initialized!\n");
1077
1118
  print(" Next steps:");
1078
1119
  print(" ┌──────────────────────────────────────────────────────────────┐");
1079
1120
  print(" │ git add .claude .github .sticky-note .gitignore .gitattributes CLAUDE.md │");
1080
- print(" │ git commit -m \"feat: add sticky-note v2 hooks\" │");
1121
+ print(" │ git commit -m \"feat: add sticky-note hooks\" │");
1081
1122
  print(" │ git push │");
1082
1123
  print(" └──────────────────────────────────────────────────────────────┘");
1083
1124
  print("");
1084
- print(" Teammates just need to `git pull` — no extra setup.");
1125
+ if (cloudProvisionedUrl) {
1126
+ print(" Teammates: git pull → run `npx sticky-note init` → enter API key when prompted.");
1127
+ print(" Restart Claude Code to activate the sticky-note MCP server.");
1128
+ } else if (configStickyUrl) {
1129
+ print(" Restart Claude Code to activate the sticky-note MCP server.");
1130
+ } else {
1131
+ print(" Teammates just need to `git pull` — no extra setup.");
1132
+ }
1085
1133
  print("");
1086
1134
  }
1087
1135
 
@@ -2952,25 +3000,43 @@ function provisionCloudBackend() {
2952
3000
  return { error: "sticky-server/worker.js not found. Make sure you have the full sticky-note package." };
2953
3001
  }
2954
3002
 
2955
- // Step 1: KV namespace
3003
+ // Step 1: KV namespace — create or reuse existing
2956
3004
  print(" Step 1: Creating KV namespace...");
2957
- let kvResult;
3005
+ let namespaceId = null;
2958
3006
  try {
2959
- kvResult = execSync("wrangler kv:namespace create STICKY_KV", {
3007
+ const kvResult = execSync("wrangler kv namespace create STICKY_KV", {
2960
3008
  cwd: serverDir,
2961
3009
  encoding: "utf-8",
2962
3010
  stdio: ["pipe", "pipe", "pipe"],
2963
3011
  });
3012
+ const idMatch = kvResult.match(/id\s*=\s*"([^"]+)"/);
3013
+ if (idMatch) namespaceId = idMatch[1];
2964
3014
  print(" [OK] KV namespace created");
2965
3015
  } catch (err) {
2966
- return { error: "Failed to create KV namespace: " + (err.message || err) + "\n Make sure you're logged in: wrangler login" };
3016
+ if (err.message && err.message.includes("already exists")) {
3017
+ // Reuse the existing namespace
3018
+ print(" [OK] KV namespace already exists — retrieving ID...");
3019
+ try {
3020
+ const listResult = execSync("wrangler kv namespace list", {
3021
+ cwd: serverDir,
3022
+ encoding: "utf-8",
3023
+ stdio: ["pipe", "pipe", "pipe"],
3024
+ });
3025
+ const namespaces = JSON.parse(listResult);
3026
+ const existing = namespaces.find(ns => ns.title === "STICKY_KV");
3027
+ if (existing) namespaceId = existing.id;
3028
+ } catch (listErr) {
3029
+ return { error: "KV namespace exists but could not retrieve ID: " + (listErr.message || listErr) };
3030
+ }
3031
+ } else {
3032
+ return { error: "Failed to create KV namespace: " + (err.message || err) + "\n Make sure you're logged in: wrangler login" };
3033
+ }
2967
3034
  }
2968
3035
 
2969
- const idMatch = kvResult.match(/id\s*=\s*"([^"]+)"/);
2970
- if (idMatch) {
3036
+ if (namespaceId) {
2971
3037
  const tomlPath = path.join(serverDir, "wrangler.toml");
2972
3038
  let toml = fs.readFileSync(tomlPath, "utf-8");
2973
- toml = toml.replace("PLACEHOLDER_KV_NAMESPACE_ID", idMatch[1]);
3039
+ toml = toml.replace("PLACEHOLDER_KV_NAMESPACE_ID", namespaceId);
2974
3040
  fs.writeFileSync(tomlPath, toml);
2975
3041
  print(" [OK] wrangler.toml updated with namespace ID");
2976
3042
  }
@@ -3020,6 +3086,17 @@ function provisionCloudBackend() {
3020
3086
  const envPath = path.join(process.cwd(), ".env.sticky");
3021
3087
  fs.writeFileSync(envPath, `STICKY_URL=${workerUrl}\nSTICKY_API_KEY=${apiKey}\n`, "utf-8");
3022
3088
  print(" [OK] .env.sticky written with STICKY_URL and STICKY_API_KEY");
3089
+
3090
+ // Write sticky_url to committed config so teammates get it on git pull
3091
+ const configPath = path.join(process.cwd(), ".sticky-note", "sticky-note-config.json");
3092
+ if (fs.existsSync(configPath)) {
3093
+ try {
3094
+ const cfg = readJsonSafe(configPath, {});
3095
+ cfg.sticky_url = workerUrl;
3096
+ fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
3097
+ print(" [OK] sticky_url written to .sticky-note/sticky-note-config.json (commit this!)");
3098
+ } catch (_) {}
3099
+ }
3023
3100
  }
3024
3101
 
3025
3102
  return { workerUrl, apiKey, secretSet };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sticky-note-cli",
3
- "version": "3.1.5",
3
+ "version": "3.1.7",
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": {
@@ -34,6 +34,6 @@
34
34
  "test": "node test/smoke.test.js"
35
35
  },
36
36
  "devDependencies": {
37
- "sticky-note-cli": "^3.1.3"
37
+ "sticky-note-cli": "^3.1.5"
38
38
  }
39
39
  }
@@ -3,6 +3,7 @@
3
3
 
4
4
  # Sticky Note - local overrides
5
5
  .claude/settings.local.json
6
+ .env.sticky
6
7
 
7
8
  # Sticky Note - transient session/resume signals
8
9
  .sticky-note/.sticky-session