clawdex-mobile 1.1.2 → 1.3.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.
package/README.md CHANGED
@@ -207,6 +207,10 @@ Published CLI equivalent:
207
207
  - `npm install -g clawdex-mobile@latest` — install/upgrade the published CLI
208
208
  - `clawdex init` — full interactive onboarding + auto-start
209
209
  - `clawdex stop` — stop running Expo + bridge for this project
210
+ - `clawdex upgrade` / `clawdex update` — upgrade global CLI package to latest
211
+ - `clawdex upgrade --version 1.1.2` — upgrade to a specific published version
212
+ - `clawdex upgrade --restart` — stop services, upgrade, then restart via `clawdex init`
213
+ - `clawdex version` — print installed CLI version
210
214
  - `clawdex init --no-start` — onboarding without launching bridge/expo
211
215
  - `clawdex init --platform ios` — auto-start with iOS target
212
216
 
@@ -131,7 +131,6 @@ const RUN_WATCHDOG_MS = 60_000;
131
131
  const LIKELY_RUNNING_RECENT_UPDATE_MS = 30_000;
132
132
  const ACTIVE_CHAT_SYNC_INTERVAL_MS = 2_000;
133
133
  const IDLE_CHAT_SYNC_INTERVAL_MS = 2_500;
134
- const THREAD_RESUME_RETRY_MS = 1_500;
135
134
  const CHAT_MODEL_PREFERENCES_FILE = 'chat-model-preferences.json';
136
135
  const CHAT_MODEL_PREFERENCES_VERSION = 1;
137
136
  const INLINE_OPTION_LINE_PATTERN =
@@ -451,7 +450,6 @@ export const MainScreen = forwardRef<MainScreenHandle, MainScreenProps>(
451
450
  const externalStatusFullSyncNextAllowedAtRef = useRef(0);
452
451
  const threadRuntimeSnapshotsRef = useRef<Record<string, ThreadRuntimeSnapshot>>({});
453
452
  const threadReasoningBuffersRef = useRef<Record<string, string>>({});
454
- const threadResumeLastAttemptAtRef = useRef<Record<string, number>>({});
455
453
  const chatModelPreferencesRef = useRef<Record<string, ChatModelPreference>>({});
456
454
  const [chatModelPreferencesLoaded, setChatModelPreferencesLoaded] = useState(false);
457
455
  const preferredStartCwd = normalizeWorkspacePath(defaultStartCwd);
@@ -612,29 +610,6 @@ export const MainScreen = forwardRef<MainScreenHandle, MainScreenProps>(
612
610
  externalStatusFullSyncQueuedThreadRef.current = null;
613
611
  }, []);
614
612
 
615
- const ensureThreadResumeSubscription = useCallback(
616
- (threadId: string, options?: { force?: boolean }) => {
617
- const normalizedThreadId = threadId.trim();
618
- if (!normalizedThreadId) {
619
- return;
620
- }
621
-
622
- const now = Date.now();
623
- const lastAttemptAt = threadResumeLastAttemptAtRef.current[normalizedThreadId] ?? 0;
624
- if (!options?.force && now - lastAttemptAt < THREAD_RESUME_RETRY_MS) {
625
- return;
626
- }
627
-
628
- threadResumeLastAttemptAtRef.current[normalizedThreadId] = now;
629
- api
630
- .resumeThread(normalizedThreadId, {
631
- approvalPolicy: activeApprovalPolicy,
632
- })
633
- .catch(() => {});
634
- },
635
- [activeApprovalPolicy, api]
636
- );
637
-
638
613
  const drainExternalStatusFullSyncQueue = useCallback(() => {
639
614
  if (externalStatusFullSyncInFlightRef.current) {
640
615
  return;
@@ -4084,10 +4059,6 @@ export const MainScreen = forwardRef<MainScreenHandle, MainScreenProps>(
4084
4059
  const threadId =
4085
4060
  readString(params?.threadId) ?? readString(params?.thread_id);
4086
4061
  if (threadId && threadId === currentId) {
4087
- // External clients (CLI/TUI) may start turns without pushing full live
4088
- // notifications through this app-server process. Force a lightweight
4089
- // resume attempt to attach to fresh stream state early.
4090
- ensureThreadResumeSubscription(threadId, { force: true });
4091
4062
  api
4092
4063
  .getChatSummary(threadId)
4093
4064
  .then((summary) => {
@@ -4107,7 +4078,6 @@ export const MainScreen = forwardRef<MainScreenHandle, MainScreenProps>(
4107
4078
  });
4108
4079
 
4109
4080
  if (isChatSummaryLikelyRunning(summary)) {
4110
- ensureThreadResumeSubscription(threadId);
4111
4081
  bumpRunWatchdog();
4112
4082
  setActivity((prev) =>
4113
4083
  prev.tone === 'running'
@@ -4176,7 +4146,6 @@ export const MainScreen = forwardRef<MainScreenHandle, MainScreenProps>(
4176
4146
  clearRunWatchdog,
4177
4147
  refreshPendingApprovalsForThread,
4178
4148
  scheduleExternalStatusFullSync,
4179
- ensureThreadResumeSubscription,
4180
4149
  registerTurnStarted,
4181
4150
  pushActiveCommand,
4182
4151
  upsertThreadRuntimeSnapshot,
@@ -4214,12 +4183,6 @@ export const MainScreen = forwardRef<MainScreenHandle, MainScreenProps>(
4214
4183
  const shouldRunFromWatchdog = runWatchdogUntilRef.current > Date.now();
4215
4184
  const shouldShowRunning = shouldRunFromChat || shouldRunFromWatchdog;
4216
4185
 
4217
- // Keep a light resume heartbeat even while idle so externally-started
4218
- // turns are discovered quickly and can stream status/tool updates.
4219
- ensureThreadResumeSubscription(selectedChatId, {
4220
- force: shouldRunFromChat,
4221
- });
4222
-
4223
4186
  if (shouldShowRunning && !hasPendingApproval && !hasPendingUserInput) {
4224
4187
  setActivity((prev) => {
4225
4188
  // Only guard against watchdog-only bumps overriding a fresh
@@ -4304,7 +4267,6 @@ export const MainScreen = forwardRef<MainScreenHandle, MainScreenProps>(
4304
4267
  pendingUserInputRequest?.id,
4305
4268
  bumpRunWatchdog,
4306
4269
  clearRunWatchdog,
4307
- ensureThreadResumeSubscription,
4308
4270
  ]);
4309
4271
 
4310
4272
  const handleResolveApproval = useCallback(
package/bin/clawdex.js CHANGED
@@ -4,6 +4,7 @@
4
4
  const { spawnSync } = require("node:child_process");
5
5
  const fs = require("node:fs");
6
6
  const path = require("node:path");
7
+ const os = require("node:os");
7
8
 
8
9
  function printUsage() {
9
10
  console.log(`Usage: clawdex <command> [options]
@@ -17,30 +18,59 @@ Commands:
17
18
  stop
18
19
  Stop bridge + Expo services for this project.
19
20
 
21
+ upgrade [--version <latest|x.y.z>] [--restart]
22
+ update [--version <latest|x.y.z>] [--restart]
23
+ Upgrade clawdex-mobile globally.
24
+ --restart stops running services first, upgrades, then runs 'clawdex init'.
25
+
26
+ version
27
+ Print current CLI package version.
28
+
20
29
  help
21
30
  Show this help.
22
31
  `);
23
32
  }
24
33
 
25
- function runScript(scriptName, args = []) {
26
- const scriptPath = path.resolve(__dirname, "..", "scripts", scriptName);
27
- if (!fs.existsSync(scriptPath)) {
28
- console.error(`error: script not found at ${scriptPath}`);
29
- process.exit(1);
30
- }
31
-
32
- const child = spawnSync(scriptPath, args, {
34
+ function runCommand(command, args = [], options = {}) {
35
+ const child = spawnSync(command, args, {
33
36
  stdio: "inherit",
34
37
  env: process.env,
35
38
  cwd: process.cwd(),
39
+ ...options,
36
40
  });
37
41
 
38
42
  if (child.error) {
39
- console.error(`error: failed to run ${scriptName}: ${child.error.message}`);
43
+ return {
44
+ ok: false,
45
+ status: child.status ?? 1,
46
+ error: child.error,
47
+ };
48
+ }
49
+
50
+ return {
51
+ ok: (child.status ?? 1) === 0,
52
+ status: child.status ?? 1,
53
+ error: null,
54
+ };
55
+ }
56
+
57
+ function runScript(scriptName, args = [], { exitOnComplete = true } = {}) {
58
+ const scriptPath = path.resolve(__dirname, "..", "scripts", scriptName);
59
+ if (!fs.existsSync(scriptPath)) {
60
+ console.error(`error: script not found at ${scriptPath}`);
40
61
  process.exit(1);
41
62
  }
42
63
 
43
- process.exit(child.status ?? 1);
64
+ const result = runCommand(scriptPath, args);
65
+ if (!result.ok && result.error) {
66
+ console.error(`error: failed to run ${scriptName}: ${result.error.message}`);
67
+ }
68
+
69
+ if (exitOnComplete) {
70
+ process.exit(result.status);
71
+ }
72
+
73
+ return result;
44
74
  }
45
75
 
46
76
  function runInit(args) {
@@ -51,6 +81,106 @@ function runStop(args) {
51
81
  runScript("stop-services.sh", args);
52
82
  }
53
83
 
84
+ function getCliVersion() {
85
+ const packageJsonPath = path.resolve(__dirname, "..", "package.json");
86
+ try {
87
+ const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
88
+ return parsed.version || "unknown";
89
+ } catch {
90
+ return "unknown";
91
+ }
92
+ }
93
+
94
+ function parseUpgradeArgs(args) {
95
+ let targetVersion = "latest";
96
+ let restart = false;
97
+ let noStop = false;
98
+
99
+ for (let i = 0; i < args.length; i += 1) {
100
+ const value = args[i];
101
+ if (value === "--version") {
102
+ const candidate = args[i + 1];
103
+ if (!candidate || candidate.startsWith("-")) {
104
+ throw new Error("--version requires a value (for example: latest, 1.1.2)");
105
+ }
106
+ targetVersion = candidate;
107
+ i += 1;
108
+ continue;
109
+ }
110
+
111
+ if (value === "--restart") {
112
+ restart = true;
113
+ continue;
114
+ }
115
+
116
+ if (value === "--no-stop") {
117
+ noStop = true;
118
+ continue;
119
+ }
120
+
121
+ if (value === "--help" || value === "-h") {
122
+ printUsage();
123
+ process.exit(0);
124
+ }
125
+
126
+ throw new Error(`unknown option '${value}'`);
127
+ }
128
+
129
+ return { targetVersion, restart, noStop };
130
+ }
131
+
132
+ function runUpgrade(args) {
133
+ let options;
134
+ try {
135
+ options = parseUpgradeArgs(args);
136
+ } catch (error) {
137
+ console.error(`error: ${error.message}`);
138
+ process.exit(1);
139
+ }
140
+
141
+ const previousVersion = getCliVersion();
142
+ const packageSpecifier =
143
+ options.targetVersion === "latest"
144
+ ? "clawdex-mobile@latest"
145
+ : `clawdex-mobile@${options.targetVersion}`;
146
+
147
+ console.log(`Current clawdex-mobile version: ${previousVersion}`);
148
+ if (!options.noStop) {
149
+ console.log("Stopping running bridge/Expo services before upgrade...");
150
+ const stopResult = runScript("stop-services.sh", [], { exitOnComplete: false });
151
+ if (!stopResult.ok) {
152
+ console.error("error: failed to stop services before upgrade.");
153
+ process.exit(stopResult.status);
154
+ }
155
+ }
156
+
157
+ console.log(`Upgrading via npm: ${packageSpecifier}`);
158
+ const installResult = runCommand("npm", ["install", "-g", packageSpecifier]);
159
+ if (!installResult.ok) {
160
+ const platformHint =
161
+ os.platform() === "win32"
162
+ ? "Run terminal as Administrator and retry."
163
+ : "If this is a permissions error, retry with sudo or fix npm global prefix.";
164
+ console.error(`error: upgrade failed. ${platformHint}`);
165
+ process.exit(installResult.status);
166
+ }
167
+
168
+ console.log("Upgrade completed.");
169
+ if (options.restart) {
170
+ console.log("Restarting with updated setup: clawdex init");
171
+ const restartResult = runCommand("clawdex", ["init"]);
172
+ process.exit(restartResult.status);
173
+ }
174
+
175
+ console.log("Run 'clawdex init' to start services with the updated version.");
176
+ process.exit(0);
177
+ }
178
+
179
+ function runVersion() {
180
+ console.log(getCliVersion());
181
+ process.exit(0);
182
+ }
183
+
54
184
  const argv = process.argv.slice(2);
55
185
  const command = argv[0];
56
186
 
@@ -67,6 +197,14 @@ if (command === "stop") {
67
197
  runStop(argv.slice(1));
68
198
  }
69
199
 
200
+ if (command === "upgrade" || command === "update") {
201
+ runUpgrade(argv.slice(1));
202
+ }
203
+
204
+ if (command === "version" || command === "--version" || command === "-v") {
205
+ runVersion();
206
+ }
207
+
70
208
  console.error(`error: unknown command '${command}'`);
71
209
  printUsage();
72
210
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdex-mobile",
3
- "version": "1.1.2",
3
+ "version": "1.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },