sticky-note-cli 3.1.31 → 3.1.32

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
@@ -1286,7 +1286,7 @@ async function cmdInit() {
1286
1286
  // UPDATE command
1287
1287
  // ──────────────────────────────────────────────
1288
1288
 
1289
- function cmdUpdate() {
1289
+ async function cmdUpdate() {
1290
1290
  printBanner();
1291
1291
 
1292
1292
  const claudeHooksDir = path.join(process.cwd(), ".claude", "hooks");
@@ -1557,7 +1557,9 @@ function cmdUpdate() {
1557
1557
  print(" [OK] .github/copilot-instructions.md (sticky-note section updated)");
1558
1558
  }
1559
1559
 
1560
- print(" ⚠️ Thread data was NOT modified.\n");
1560
+ // ── Auto-migration: upgrade to data branch + push to cloud if needed ────────
1561
+ // Runs silently as part of update so the user doesn't need manual follow-up.
1562
+ await _autoMigrateIfNeeded();
1561
1563
  }
1562
1564
 
1563
1565
  // ──────────────────────────────────────────────
@@ -4555,6 +4557,100 @@ async function cmdMigrate() {
4555
4557
  return _cmdMigrateDataBranch();
4556
4558
  }
4557
4559
 
4560
+ /**
4561
+ * Called automatically at the end of cmdUpdate(). Detects two cases and
4562
+ * handles them without requiring manual follow-up commands:
4563
+ *
4564
+ * 1. Data-branch is empty but in-tree .sticky-note/ has threads
4565
+ * → runs _cmdMigrateDataBranch() automatically.
4566
+ *
4567
+ * 2. Cloud (STICKY_URL) is configured and cloud has fewer threads than local
4568
+ * → pushes all local threads to the cloud Worker.
4569
+ */
4570
+ async function _autoMigrateIfNeeded() {
4571
+ const cwd = process.cwd();
4572
+
4573
+ // ── 1. Data branch migration ─────────────────────────────────────────────
4574
+ try {
4575
+ const gitDir = execFileSync("git", ["rev-parse", "--absolute-git-dir"], {
4576
+ encoding: "utf-8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"], cwd,
4577
+ }).trim();
4578
+ const dataBranchMemory = path.join(gitDir, "sticky-note", "sticky-note.json");
4579
+ const inTreeMemory = path.join(cwd, ".sticky-note", "sticky-note.json");
4580
+ const dataBranchCount = fs.existsSync(dataBranchMemory)
4581
+ ? (readJsonSafe(dataBranchMemory, { threads: [] }).threads || []).length
4582
+ : 0;
4583
+ const inTreeCount = fs.existsSync(inTreeMemory)
4584
+ ? (readJsonSafe(inTreeMemory, { threads: [] }).threads || []).length
4585
+ : 0;
4586
+ if (dataBranchCount === 0 && inTreeCount > 0) {
4587
+ print(`\n 🔄 Found ${inTreeCount} thread(s) in .sticky-note/ — migrating to data branch...\n`);
4588
+ await _cmdMigrateDataBranch();
4589
+ }
4590
+ } catch (_) { /* non-fatal — skip if not a git repo or migration fails */ }
4591
+
4592
+ // ── 2. Cloud push ─────────────────────────────────────────────────────────
4593
+ // Read STICKY_URL from .env.sticky (project root).
4594
+ const envStickyPath = path.join(cwd, ".env.sticky");
4595
+ if (!fs.existsSync(envStickyPath)) return;
4596
+
4597
+ let stickyUrl = "";
4598
+ let stickyApiKey = "";
4599
+ for (const line of fs.readFileSync(envStickyPath, "utf-8").split(/\r?\n/)) {
4600
+ const t = line.trim();
4601
+ if (t.startsWith("STICKY_URL=")) stickyUrl = t.slice("STICKY_URL=".length).trim();
4602
+ if (t.startsWith("STICKY_API_KEY=")) stickyApiKey = t.slice("STICKY_API_KEY=".length).trim();
4603
+ }
4604
+ if (!stickyUrl) return;
4605
+
4606
+ // Detect project name from git remote
4607
+ let projectName = "default";
4608
+ try {
4609
+ const remote = execFileSync("git", ["remote", "get-url", "origin"], {
4610
+ encoding: "utf-8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"], cwd,
4611
+ }).trim();
4612
+ const match = remote.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/);
4613
+ if (match) projectName = match[1];
4614
+ } catch (_) {}
4615
+
4616
+ const headers = { "Content-Type": "application/json", "X-Sticky-Project": projectName };
4617
+ if (stickyApiKey) headers["X-Sticky-API-Key"] = stickyApiKey;
4618
+
4619
+ // Compare local vs cloud thread counts
4620
+ let cloudCount = 0;
4621
+ try {
4622
+ const resp = await fetch(`${stickyUrl}/threads`, { method: "GET", headers });
4623
+ if (resp.ok) {
4624
+ const data = await resp.json();
4625
+ cloudCount = (data.threads || []).length;
4626
+ }
4627
+ } catch (_) { return; } // Cloud unreachable — skip silently
4628
+
4629
+ const localMemoryPath = path.join(getActiveStickyDir(), "sticky-note.json");
4630
+ const localMemory = readJsonSafe(localMemoryPath, { threads: [] });
4631
+ const localThreads = localMemory.threads || [];
4632
+ if (localThreads.length <= cloudCount) return; // Cloud already up to date
4633
+
4634
+ print(`\n ☁️ Auto-pushing ${localThreads.length} thread(s) to cloud (cloud has ${cloudCount})...\n`);
4635
+ let pushed = 0;
4636
+ let failed = 0;
4637
+ for (const thread of localThreads) {
4638
+ try {
4639
+ const r = await fetch(`${stickyUrl}/threads/${thread.id}`, {
4640
+ method: "PUT", headers, body: JSON.stringify(thread),
4641
+ });
4642
+ if (r.ok) pushed++;
4643
+ else failed++;
4644
+ } catch (_) { failed++; }
4645
+ }
4646
+ if (failed > 0) {
4647
+ print(` [WARN] Cloud push: ${pushed} succeeded, ${failed} failed.`);
4648
+ print(` Run \`npx sticky-note migrate --to cloud\` to retry.\n`);
4649
+ } else {
4650
+ print(` [OK] ${pushed} thread(s) synced to cloud.\n`);
4651
+ }
4652
+ }
4653
+
4558
4654
  /**
4559
4655
  * One-time migration from .sticky-note/ (feature-branch storage) to
4560
4656
  * .git/sticky-note/ (data-branch storage).
@@ -4816,7 +4912,7 @@ async function main() {
4816
4912
  await cmdInit();
4817
4913
  break;
4818
4914
  case "update":
4819
- cmdUpdate();
4915
+ await cmdUpdate();
4820
4916
  break;
4821
4917
  case "status":
4822
4918
  cmdStatus();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sticky-note-cli",
3
- "version": "3.1.31",
3
+ "version": "3.1.32",
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": {
@@ -1,28 +0,0 @@
1
- const urls = new Set();
2
-
3
- function checkURL(request, init) {
4
- const url =
5
- request instanceof URL
6
- ? request
7
- : new URL(
8
- (typeof request === "string" ? new Request(request, init) : request)
9
- .url
10
- );
11
- if (url.port && url.port !== "443" && url.protocol === "https:") {
12
- if (!urls.has(url.toString())) {
13
- urls.add(url.toString());
14
- console.warn(
15
- `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers:\n` +
16
- ` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command.\n`
17
- );
18
- }
19
- }
20
- }
21
-
22
- globalThis.fetch = new Proxy(globalThis.fetch, {
23
- apply(target, thisArg, argArray) {
24
- const [request, init] = argArray;
25
- checkURL(request, init);
26
- return Reflect.apply(target, thisArg, argArray);
27
- },
28
- });
@@ -1,11 +0,0 @@
1
- import worker, * as OTHER_EXPORTS from "C:\\Users\\dheer\\OneDrive\\Desktop\\Projects\\sticky-note\\sticky-server\\worker.js";
2
- import * as __MIDDLEWARE_0__ from "C:\\Users\\dheer\\AppData\\Roaming\\npm\\node_modules\\wrangler\\templates\\middleware\\middleware-ensure-req-body-drained.ts";
3
- import * as __MIDDLEWARE_1__ from "C:\\Users\\dheer\\AppData\\Roaming\\npm\\node_modules\\wrangler\\templates\\middleware\\middleware-miniflare3-json-error.ts";
4
-
5
- export * from "C:\\Users\\dheer\\OneDrive\\Desktop\\Projects\\sticky-note\\sticky-server\\worker.js";
6
- const MIDDLEWARE_TEST_INJECT = "__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__";
7
- export const __INTERNAL_WRANGLER_MIDDLEWARE__ = [
8
-
9
- __MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default
10
- ]
11
- export default worker;
@@ -1,134 +0,0 @@
1
- // This loads all middlewares exposed on the middleware object and then starts
2
- // the invocation chain. The big idea is that we can add these to the middleware
3
- // export dynamically through wrangler, or we can potentially let users directly
4
- // add them as a sort of "plugin" system.
5
-
6
- import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "C:\\Users\\dheer\\OneDrive\\Desktop\\Projects\\sticky-note\\sticky-server\\.wrangler\\tmp\\bundle-GEjBtr\\middleware-insertion-facade.js";
7
- import { __facade_invoke__, __facade_register__, Dispatcher } from "C:\\Users\\dheer\\AppData\\Roaming\\npm\\node_modules\\wrangler\\templates\\middleware\\common.ts";
8
- import type { WorkerEntrypointConstructor } from "C:\\Users\\dheer\\OneDrive\\Desktop\\Projects\\sticky-note\\sticky-server\\.wrangler\\tmp\\bundle-GEjBtr\\middleware-insertion-facade.js";
9
-
10
- // Preserve all the exports from the worker
11
- export * from "C:\\Users\\dheer\\OneDrive\\Desktop\\Projects\\sticky-note\\sticky-server\\.wrangler\\tmp\\bundle-GEjBtr\\middleware-insertion-facade.js";
12
-
13
- class __Facade_ScheduledController__ implements ScheduledController {
14
- readonly #noRetry: ScheduledController["noRetry"];
15
-
16
- constructor(
17
- readonly scheduledTime: number,
18
- readonly cron: string,
19
- noRetry: ScheduledController["noRetry"]
20
- ) {
21
- this.#noRetry = noRetry;
22
- }
23
-
24
- noRetry() {
25
- if (!(this instanceof __Facade_ScheduledController__)) {
26
- throw new TypeError("Illegal invocation");
27
- }
28
- // Need to call native method immediately in case uncaught error thrown
29
- this.#noRetry();
30
- }
31
- }
32
-
33
- function wrapExportedHandler(worker: ExportedHandler): ExportedHandler {
34
- // If we don't have any middleware defined, just return the handler as is
35
- if (
36
- __INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||
37
- __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0
38
- ) {
39
- return worker;
40
- }
41
- // Otherwise, register all middleware once
42
- for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {
43
- __facade_register__(middleware);
44
- }
45
-
46
- const fetchDispatcher: ExportedHandlerFetchHandler = function (
47
- request,
48
- env,
49
- ctx
50
- ) {
51
- if (worker.fetch === undefined) {
52
- throw new Error("Handler does not export a fetch() function.");
53
- }
54
- return worker.fetch(request, env, ctx);
55
- };
56
-
57
- return {
58
- ...worker,
59
- fetch(request, env, ctx) {
60
- const dispatcher: Dispatcher = function (type, init) {
61
- if (type === "scheduled" && worker.scheduled !== undefined) {
62
- const controller = new __Facade_ScheduledController__(
63
- Date.now(),
64
- init.cron ?? "",
65
- () => {}
66
- );
67
- return worker.scheduled(controller, env, ctx);
68
- }
69
- };
70
- return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);
71
- },
72
- };
73
- }
74
-
75
- function wrapWorkerEntrypoint(
76
- klass: WorkerEntrypointConstructor
77
- ): WorkerEntrypointConstructor {
78
- // If we don't have any middleware defined, just return the handler as is
79
- if (
80
- __INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||
81
- __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0
82
- ) {
83
- return klass;
84
- }
85
- // Otherwise, register all middleware once
86
- for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {
87
- __facade_register__(middleware);
88
- }
89
-
90
- // `extend`ing `klass` here so other RPC methods remain callable
91
- return class extends klass {
92
- #fetchDispatcher: ExportedHandlerFetchHandler<Record<string, unknown>> = (
93
- request,
94
- env,
95
- ctx
96
- ) => {
97
- this.env = env;
98
- this.ctx = ctx;
99
- if (super.fetch === undefined) {
100
- throw new Error("Entrypoint class does not define a fetch() function.");
101
- }
102
- return super.fetch(request);
103
- };
104
-
105
- #dispatcher: Dispatcher = (type, init) => {
106
- if (type === "scheduled" && super.scheduled !== undefined) {
107
- const controller = new __Facade_ScheduledController__(
108
- Date.now(),
109
- init.cron ?? "",
110
- () => {}
111
- );
112
- return super.scheduled(controller);
113
- }
114
- };
115
-
116
- fetch(request: Request<unknown, IncomingRequestCfProperties>) {
117
- return __facade_invoke__(
118
- request,
119
- this.env,
120
- this.ctx,
121
- this.#dispatcher,
122
- this.#fetchDispatcher
123
- );
124
- }
125
- };
126
- }
127
-
128
- let WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;
129
- if (typeof ENTRY === "object") {
130
- WRAPPED_ENTRY = wrapExportedHandler(ENTRY);
131
- } else if (typeof ENTRY === "function") {
132
- WRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);
133
- }
134
- export default WRAPPED_ENTRY;
@@ -1,28 +0,0 @@
1
- const urls = new Set();
2
-
3
- function checkURL(request, init) {
4
- const url =
5
- request instanceof URL
6
- ? request
7
- : new URL(
8
- (typeof request === "string" ? new Request(request, init) : request)
9
- .url
10
- );
11
- if (url.port && url.port !== "443" && url.protocol === "https:") {
12
- if (!urls.has(url.toString())) {
13
- urls.add(url.toString());
14
- console.warn(
15
- `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers:\n` +
16
- ` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command.\n`
17
- );
18
- }
19
- }
20
- }
21
-
22
- globalThis.fetch = new Proxy(globalThis.fetch, {
23
- apply(target, thisArg, argArray) {
24
- const [request, init] = argArray;
25
- checkURL(request, init);
26
- return Reflect.apply(target, thisArg, argArray);
27
- },
28
- });
@@ -1,11 +0,0 @@
1
- import worker, * as OTHER_EXPORTS from "C:\\Users\\dheer\\OneDrive\\Desktop\\Projects\\sticky-note\\sticky-server\\worker.js";
2
- import * as __MIDDLEWARE_0__ from "C:\\Users\\dheer\\AppData\\Roaming\\npm\\node_modules\\wrangler\\templates\\middleware\\middleware-ensure-req-body-drained.ts";
3
- import * as __MIDDLEWARE_1__ from "C:\\Users\\dheer\\AppData\\Roaming\\npm\\node_modules\\wrangler\\templates\\middleware\\middleware-miniflare3-json-error.ts";
4
-
5
- export * from "C:\\Users\\dheer\\OneDrive\\Desktop\\Projects\\sticky-note\\sticky-server\\worker.js";
6
- const MIDDLEWARE_TEST_INJECT = "__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__";
7
- export const __INTERNAL_WRANGLER_MIDDLEWARE__ = [
8
-
9
- __MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default
10
- ]
11
- export default worker;
@@ -1,134 +0,0 @@
1
- // This loads all middlewares exposed on the middleware object and then starts
2
- // the invocation chain. The big idea is that we can add these to the middleware
3
- // export dynamically through wrangler, or we can potentially let users directly
4
- // add them as a sort of "plugin" system.
5
-
6
- import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "C:\\Users\\dheer\\OneDrive\\Desktop\\Projects\\sticky-note\\sticky-server\\.wrangler\\tmp\\bundle-seUgBC\\middleware-insertion-facade.js";
7
- import { __facade_invoke__, __facade_register__, Dispatcher } from "C:\\Users\\dheer\\AppData\\Roaming\\npm\\node_modules\\wrangler\\templates\\middleware\\common.ts";
8
- import type { WorkerEntrypointConstructor } from "C:\\Users\\dheer\\OneDrive\\Desktop\\Projects\\sticky-note\\sticky-server\\.wrangler\\tmp\\bundle-seUgBC\\middleware-insertion-facade.js";
9
-
10
- // Preserve all the exports from the worker
11
- export * from "C:\\Users\\dheer\\OneDrive\\Desktop\\Projects\\sticky-note\\sticky-server\\.wrangler\\tmp\\bundle-seUgBC\\middleware-insertion-facade.js";
12
-
13
- class __Facade_ScheduledController__ implements ScheduledController {
14
- readonly #noRetry: ScheduledController["noRetry"];
15
-
16
- constructor(
17
- readonly scheduledTime: number,
18
- readonly cron: string,
19
- noRetry: ScheduledController["noRetry"]
20
- ) {
21
- this.#noRetry = noRetry;
22
- }
23
-
24
- noRetry() {
25
- if (!(this instanceof __Facade_ScheduledController__)) {
26
- throw new TypeError("Illegal invocation");
27
- }
28
- // Need to call native method immediately in case uncaught error thrown
29
- this.#noRetry();
30
- }
31
- }
32
-
33
- function wrapExportedHandler(worker: ExportedHandler): ExportedHandler {
34
- // If we don't have any middleware defined, just return the handler as is
35
- if (
36
- __INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||
37
- __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0
38
- ) {
39
- return worker;
40
- }
41
- // Otherwise, register all middleware once
42
- for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {
43
- __facade_register__(middleware);
44
- }
45
-
46
- const fetchDispatcher: ExportedHandlerFetchHandler = function (
47
- request,
48
- env,
49
- ctx
50
- ) {
51
- if (worker.fetch === undefined) {
52
- throw new Error("Handler does not export a fetch() function.");
53
- }
54
- return worker.fetch(request, env, ctx);
55
- };
56
-
57
- return {
58
- ...worker,
59
- fetch(request, env, ctx) {
60
- const dispatcher: Dispatcher = function (type, init) {
61
- if (type === "scheduled" && worker.scheduled !== undefined) {
62
- const controller = new __Facade_ScheduledController__(
63
- Date.now(),
64
- init.cron ?? "",
65
- () => {}
66
- );
67
- return worker.scheduled(controller, env, ctx);
68
- }
69
- };
70
- return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);
71
- },
72
- };
73
- }
74
-
75
- function wrapWorkerEntrypoint(
76
- klass: WorkerEntrypointConstructor
77
- ): WorkerEntrypointConstructor {
78
- // If we don't have any middleware defined, just return the handler as is
79
- if (
80
- __INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||
81
- __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0
82
- ) {
83
- return klass;
84
- }
85
- // Otherwise, register all middleware once
86
- for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {
87
- __facade_register__(middleware);
88
- }
89
-
90
- // `extend`ing `klass` here so other RPC methods remain callable
91
- return class extends klass {
92
- #fetchDispatcher: ExportedHandlerFetchHandler<Record<string, unknown>> = (
93
- request,
94
- env,
95
- ctx
96
- ) => {
97
- this.env = env;
98
- this.ctx = ctx;
99
- if (super.fetch === undefined) {
100
- throw new Error("Entrypoint class does not define a fetch() function.");
101
- }
102
- return super.fetch(request);
103
- };
104
-
105
- #dispatcher: Dispatcher = (type, init) => {
106
- if (type === "scheduled" && super.scheduled !== undefined) {
107
- const controller = new __Facade_ScheduledController__(
108
- Date.now(),
109
- init.cron ?? "",
110
- () => {}
111
- );
112
- return super.scheduled(controller);
113
- }
114
- };
115
-
116
- fetch(request: Request<unknown, IncomingRequestCfProperties>) {
117
- return __facade_invoke__(
118
- request,
119
- this.env,
120
- this.ctx,
121
- this.#dispatcher,
122
- this.#fetchDispatcher
123
- );
124
- }
125
- };
126
- }
127
-
128
- let WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;
129
- if (typeof ENTRY === "object") {
130
- WRAPPED_ENTRY = wrapExportedHandler(ENTRY);
131
- } else if (typeof ENTRY === "function") {
132
- WRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);
133
- }
134
- export default WRAPPED_ENTRY;