@quantiya/codevibe 2.0.16 → 2.0.17

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.
Files changed (2) hide show
  1. package/bin/codevibe +387 -97
  2. package/package.json +1 -1
package/bin/codevibe CHANGED
@@ -149,85 +149,271 @@ show_version() {
149
149
 
150
150
  # ─── Update ──────────────────────────────────────────────────────────
151
151
  #
152
- # NOTE: Claude Code's plugin lifecycle has known bugs as of Claude Code 2.1.x:
153
- # 1. `claude plugin update` lies — reports "already at latest version" based on
154
- # the installed plugin's own package.json, never consults the marketplace
155
- # manifest for the authoritative latest version.
156
- # 2. `claude plugin install` doesn't refetch — if a cache directory for the
157
- # plugin exists, it reuses it silently instead of downloading the version
158
- # pinned in the marketplace manifest.
159
- # 3. `claude plugin uninstall` doesn't delete the cached plugin directory —
160
- # only removes the installed_plugins.json entry, leaving stale code on disk
161
- # for the next install to silently reuse.
162
- #
163
- # This function works around all three by detecting when the installed version
164
- # on disk doesn't match the version the marketplace says it should be, then
165
- # manually clearing all stale cache paths before reinstalling and verifying.
166
- #
167
- # If Anthropic fixes these bugs, the cache-nuking path is a no-op (the version
168
- # comparison will always match) and the code stays correct.
152
+ # Claude's supported JSON CLI is the authority for registration state. Never
153
+ # infer it from Claude's private JSON files or mutate Claude-owned cache paths.
154
+ # A mismatched registration is removed through the CLI and reinstalled from the
155
+ # exact marketplace pin, then its on-disk package identity is verified.
156
+
157
+ # CodeVibe 2.x is private dogfood on this machine until GA. Keep the npm and
158
+ # Claude marketplace channels paired; the GA cutover changes this marketplace
159
+ # to codevibe-marketplace when @quantiya/codevibe moves from @next to @latest.
160
+ CLAUDE_MARKETPLACE="codevibe-next"
161
+
162
+ _read_next_meta_version() {
163
+ local raw
164
+ raw=$(npm view @quantiya/codevibe@next version 2>/dev/null) || return 1
165
+ node -e '
166
+ const value = process.argv[1];
167
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value)) process.exit(1);
168
+ process.stdout.write(value);
169
+ ' "$raw"
170
+ }
171
+
172
+ _resolve_installed_meta_launcher() {
173
+ local expected_version="$1"
174
+ local global_root
175
+ global_root=$(npm root -g 2>/dev/null) || return 1
176
+ node -e '
177
+ const fs = require("fs");
178
+ const path = require("path");
179
+ try {
180
+ const packageDir = path.join(process.argv[1], "@quantiya", "codevibe");
181
+ const packagePath = path.join(packageDir, "package.json");
182
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
183
+ if (pkg.name !== "@quantiya/codevibe" || pkg.version !== process.argv[2]) {
184
+ throw new Error("package identity mismatch");
185
+ }
186
+ if (pkg.bin?.codevibe !== "./bin/codevibe") throw new Error("launcher declaration mismatch");
187
+ const realPackageDir = fs.realpathSync(packageDir);
188
+ const launcher = fs.realpathSync(path.resolve(packageDir, pkg.bin.codevibe));
189
+ if (!launcher.startsWith(`${realPackageDir}${path.sep}`)) throw new Error("launcher escaped package");
190
+ if (!fs.statSync(launcher).isFile()) throw new Error("launcher is not a file");
191
+ process.stdout.write(launcher);
192
+ } catch {
193
+ process.exit(1);
194
+ }
195
+ ' "$global_root" "$expected_version" 2>/dev/null
196
+ }
197
+
198
+ _read_packaged_claude_version() {
199
+ node -e '
200
+ const fs = require("fs");
201
+ try {
202
+ const pkg = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
203
+ const value = pkg.dependencies?.["@quantiya/codevibe-claude-plugin"];
204
+ if (typeof value === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value)) {
205
+ process.stdout.write(value);
206
+ process.exit(0);
207
+ }
208
+ } catch {}
209
+ process.exit(1);
210
+ ' "$PACKAGE_DIR/package.json" 2>/dev/null
211
+ }
212
+
213
+ _capture_claude_marketplaces() {
214
+ local raw
215
+ if ! raw=$(claude plugin marketplace list --json 2>/dev/null); then
216
+ return 1
217
+ fi
218
+ node -e '
219
+ const path = require("path");
220
+ try {
221
+ const rows = JSON.parse(process.argv[1]);
222
+ if (!Array.isArray(rows)) throw new Error("not an array");
223
+ const seen = new Set();
224
+ for (const row of rows) {
225
+ if (!row || typeof row !== "object" || Array.isArray(row)) throw new Error("invalid row");
226
+ if (typeof row.name !== "string" || !row.name) throw new Error("invalid name");
227
+ if (seen.has(row.name)) throw new Error("duplicate marketplace");
228
+ seen.add(row.name);
229
+ if (typeof row.installLocation !== "string" || !path.isAbsolute(row.installLocation)) {
230
+ throw new Error("invalid install location");
231
+ }
232
+ }
233
+ process.stdout.write(JSON.stringify(rows));
234
+ } catch {
235
+ process.exit(1);
236
+ }
237
+ ' "$raw"
238
+ }
239
+
240
+ _read_marketplace_root() {
241
+ local marketplace="$1"
242
+ local marketplaces="$2"
243
+ node -e '
244
+ const rows = JSON.parse(process.argv[1]);
245
+ const matches = rows.filter((row) => row.name === process.argv[2]);
246
+ if (matches.length !== 1) process.exit(1);
247
+ process.stdout.write(matches[0].installLocation);
248
+ ' "$marketplaces" "$marketplace" 2>/dev/null
249
+ }
169
250
 
170
251
  _read_marketplace_version() {
171
- # Read the pinned source.version for codevibe-claude from the marketplace manifest.
172
- # Echoes the version string, or empty on failure.
173
- local manifest="$HOME/.claude/plugins/marketplaces/codevibe-marketplace/.claude-plugin/marketplace.json"
174
- [ -f "$manifest" ] || return 0
175
- node -p "
252
+ local marketplace="$1"
253
+ local marketplaces="$2"
254
+ local root manifest
255
+ root=$(_read_marketplace_root "$marketplace" "$marketplaces") || return 1
256
+ manifest="$root/.claude-plugin/marketplace.json"
257
+ [ -f "$manifest" ] || return 1
258
+ node -e '
259
+ const fs = require("fs");
176
260
  try {
177
- const m = require('$manifest');
178
- const p = (m.plugins || []).find(x => x.name === 'codevibe-claude');
179
- (p && p.source && p.source.version) || '';
180
- } catch (e) { '' }
181
- " 2>/dev/null
261
+ const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
262
+ if (!Array.isArray(manifest.plugins)) throw new Error("missing plugins");
263
+ const matches = manifest.plugins.filter((item) => item?.name === "codevibe-claude");
264
+ if (matches.length !== 1) throw new Error("ambiguous plugin");
265
+ const plugin = matches[0];
266
+ const value = plugin?.source?.version;
267
+ if (typeof value !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value)) {
268
+ throw new Error("invalid version");
269
+ }
270
+ process.stdout.write(value);
271
+ } catch {
272
+ process.exit(1);
273
+ }
274
+ ' "$manifest" 2>/dev/null
182
275
  }
183
276
 
184
- _read_installed_version() {
185
- # Read the installed version of codevibe-claude from installed_plugins.json.
186
- # Echoes the version string, or empty if not installed.
187
- local db="$HOME/.claude/plugins/installed_plugins.json"
188
- [ -f "$db" ] || return 0
189
- node -p "
277
+ _capture_claude_plugins() {
278
+ local raw
279
+ if ! raw=$(claude plugin list --json 2>/dev/null); then
280
+ return 1
281
+ fi
282
+ node -e '
283
+ const path = require("path");
190
284
  try {
191
- const ip = require('$db');
192
- const e = (ip.plugins || {})['codevibe-claude@codevibe-marketplace'];
193
- (e && e[0] && e[0].version) || '';
194
- } catch (e) { '' }
195
- " 2>/dev/null
285
+ const rows = JSON.parse(process.argv[1]);
286
+ if (!Array.isArray(rows)) throw new Error("not an array");
287
+ const selected = [];
288
+ const seen = new Set();
289
+ const supported = new Set([
290
+ "codevibe-claude@codevibe-next",
291
+ "codevibe-claude@codevibe-marketplace",
292
+ ]);
293
+ for (const row of rows) {
294
+ if (!row || typeof row !== "object" || Array.isArray(row)) throw new Error("invalid row");
295
+ if (typeof row.id !== "string" || !row.id) throw new Error("invalid id");
296
+ if (!row.id.startsWith("codevibe-claude@")) continue;
297
+ if (!supported.has(row.id)) throw new Error("unsupported CodeVibe registration");
298
+ if (seen.has(row.id)) throw new Error("duplicate registration");
299
+ seen.add(row.id);
300
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(row.version)) throw new Error("invalid version");
301
+ if (!new Set(["user", "project", "local"]).has(row.scope)) throw new Error("invalid scope");
302
+ if (typeof row.enabled !== "boolean") throw new Error("invalid enabled state");
303
+ if (typeof row.installPath !== "string" || !path.isAbsolute(row.installPath)) throw new Error("invalid install path");
304
+ selected.push(row);
305
+ }
306
+ process.stdout.write(JSON.stringify(selected));
307
+ } catch {
308
+ process.exit(1);
309
+ }
310
+ ' "$raw"
196
311
  }
197
312
 
198
- _nuke_claude_plugin_cache() {
199
- # Remove every path Claude Code's plugin loader might read stale code from.
200
- rm -rf "$HOME/.claude/plugins/cache/codevibe-marketplace/codevibe-claude"
201
- rm -rf "$HOME/.claude/plugins/npm-cache/node_modules/@quantiya/codevibe-claude-plugin"
202
- rm -rf "$HOME/.claude/plugins/npm-cache/node_modules/codevibe-claude-plugin"
203
- rm -f "$HOME/.claude/plugins/npm-cache/package-lock.json"
313
+ _verify_only_preview_registration() {
314
+ local plugins="$1"
315
+ node -e '
316
+ const rows = JSON.parse(process.argv[1]);
317
+ if (rows.length !== 1 || rows[0].id !== "codevibe-claude@codevibe-next") process.exit(1);
318
+ ' "$plugins" 2>/dev/null
319
+ }
320
+
321
+ _read_claude_plugin_state() {
322
+ local marketplace="$1"
323
+ local plugins="$2"
324
+ node -e '
325
+ const rows = JSON.parse(process.argv[1]);
326
+ const row = rows.find((item) => item.id === `codevibe-claude@${process.argv[2]}`);
327
+ if (!row) process.exit(3);
328
+ process.stdout.write(JSON.stringify(row));
329
+ ' "$plugins" "$marketplace" 2>/dev/null
330
+ }
331
+
332
+ _claude_plugin_state_field() {
333
+ local state="$1"
334
+ local field="$2"
335
+ node -e '
336
+ const row = JSON.parse(process.argv[1]);
337
+ const value = row[process.argv[2]];
338
+ if (!["string", "boolean"].includes(typeof value)) process.exit(1);
339
+ process.stdout.write(String(value));
340
+ ' "$state" "$field" 2>/dev/null
341
+ }
342
+
343
+ _verify_claude_plugin_files() {
344
+ local state="$1"
345
+ local expected_version="$2"
346
+ node -e '
347
+ const fs = require("fs");
348
+ const path = require("path");
349
+ try {
350
+ const row = JSON.parse(process.argv[1]);
351
+ const expected = process.argv[2];
352
+ if (row.version !== expected || row.enabled !== true || row.scope !== "user") throw new Error("state mismatch");
353
+ const installPath = fs.realpathSync(row.installPath);
354
+ const packagePath = path.join(installPath, "package.json");
355
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
356
+ if (pkg.name !== "@quantiya/codevibe-claude-plugin" || pkg.version !== expected) throw new Error("package mismatch");
357
+ } catch {
358
+ process.exit(1);
359
+ }
360
+ ' "$state" "$expected_version" 2>/dev/null
204
361
  }
205
362
 
206
363
  do_update() {
207
- echo ""
208
- echo -e "${BOLD}${PURPLE}CodeVibe Update${NC}"
209
- echo ""
364
+ local update_phase="${CODEVIBE_UPDATE_AFTER_META:-meta}"
365
+ unset CODEVIBE_UPDATE_AFTER_META
366
+ case "$update_phase" in
367
+ meta|plugins) ;;
368
+ *)
369
+ echo "Invalid internal CodeVibe update phase." >&2
370
+ return 1
371
+ ;;
372
+ esac
210
373
 
211
- # Show current meta-package version
212
- if [ -f "$PACKAGE_DIR/package.json" ]; then
213
- CUR_VER=$(node -p "require('$PACKAGE_DIR/package.json').version" 2>/dev/null || echo "?")
214
- echo -e " ${DIM}Current meta-package: $CUR_VER${NC}"
215
- fi
216
- echo ""
374
+ if [ "$update_phase" = "meta" ]; then
375
+ echo ""
376
+ echo -e "${BOLD}${PURPLE}CodeVibe Update${NC}"
377
+ echo ""
217
378
 
218
- # ─── Step 1: Update the global meta-package on the CodeVibe 2.x channel ───
219
- echo -e "${PURPLE}▸${NC} Updating @quantiya/codevibe..."
220
- # Capture output so we can check npm's exit status directly instead of the
221
- # pipe's (tail's exit status would mask npm failures without `set -o pipefail`).
222
- local npm_output
223
- if ! npm_output=$(npm install -g --force @quantiya/codevibe@next 2>&1); then
224
- echo "$npm_output" | tail -10
379
+ if [ -f "$PACKAGE_DIR/package.json" ]; then
380
+ CUR_VER=$(node -p "require('$PACKAGE_DIR/package.json').version" 2>/dev/null || echo "?")
381
+ echo -e " ${DIM}Current meta-package: $CUR_VER${NC}"
382
+ fi
225
383
  echo ""
226
- echo -e "${YELLOW}!${NC} Meta-package update failed. Check the output above, fix the issue, and retry 'codevibe update'."
227
- return 1
228
384
  fi
229
- echo "$npm_output" | tail -3
230
- echo -e "${GREEN}✓${NC} Meta-package updated"
385
+
386
+ if [ "$update_phase" = "meta" ]; then
387
+ # ─── Step 1: Update the global meta-package on the CodeVibe 2.x channel ─
388
+ echo -e "${PURPLE}▸${NC} Updating @quantiya/codevibe..."
389
+ local desired_meta_version
390
+ local installed_launcher
391
+ if ! desired_meta_version=$(_read_next_meta_version); then
392
+ echo ""
393
+ echo -e "${YELLOW}!${NC} Could not resolve the exact @quantiya/codevibe@next version. No package was changed."
394
+ return 1
395
+ fi
396
+ local npm_output
397
+ if ! npm_output=$(npm install -g --force @quantiya/codevibe@next 2>&1); then
398
+ echo "$npm_output" | tail -10
399
+ echo ""
400
+ echo -e "${YELLOW}!${NC} Meta-package update failed. Check the output above, fix the issue, and retry 'codevibe update'."
401
+ return 1
402
+ fi
403
+ echo "$npm_output" | tail -3
404
+ echo -e "${GREEN}✓${NC} Meta-package updated"
405
+
406
+ if ! installed_launcher=$(_resolve_installed_meta_launcher "$desired_meta_version"); then
407
+ echo ""
408
+ echo -e "${YELLOW}!${NC} The installed CodeVibe package does not match @next ${desired_meta_version}; plugin state was not changed."
409
+ return 1
410
+ fi
411
+
412
+ # The current process still holds old functions. Re-exec the verified
413
+ # launcher inside npm's actual global package before any plugin mutation.
414
+ export CODEVIBE_UPDATE_AFTER_META=plugins
415
+ exec "$installed_launcher" update
416
+ fi
231
417
 
232
418
  # ─── Step 2: Update the Claude Code plugin ─────────────────────────────────
233
419
  if ! command -v claude >/dev/null 2>&1; then
@@ -246,37 +432,94 @@ do_update() {
246
432
  echo ""
247
433
  echo -e "${PURPLE}▸${NC} Updating Claude Code plugin..."
248
434
 
249
- # Refresh the marketplace manifest so we see the latest pinned version.
250
- # Errors here are non-fatal at this step — the previously-cached manifest may
251
- # still be usable — but the EXPECTED_VERSION read below is the real gate.
252
- if claude plugin marketplace update codevibe-marketplace; then
253
- echo -e "${GREEN}✓${NC} Marketplace refreshed"
254
- else
255
- echo -e "${YELLOW}!${NC} Marketplace refresh failed (falling back to cached manifest)"
256
- fi
257
-
435
+ local DESIRED_VERSION
258
436
  local EXPECTED_VERSION
437
+ local MARKETPLACES
438
+ local PLUGINS
439
+ local PREVIEW_STATE=""
440
+ local CONFLICT_STATE=""
441
+ local PREVIEW_PRESENT=false
442
+ local CONFLICT_PRESENT=false
259
443
  local INSTALLED_VERSION
260
- EXPECTED_VERSION=$(_read_marketplace_version)
261
- INSTALLED_VERSION=$(_read_installed_version)
444
+ local INSTALLED_ENABLED
445
+ local INSTALLED_SCOPE
446
+ local CONFLICT_SCOPE
447
+ local REGISTRATION_CHANGED=false
262
448
 
263
- # EXPECTED_VERSION is required — without it we have no ground truth to verify
264
- # against, so the whole cache-bug workaround is blind. Fail loudly rather than
265
- # claim success we can't prove.
266
- if [ -z "$EXPECTED_VERSION" ]; then
449
+ DESIRED_VERSION=$(_read_packaged_claude_version)
450
+ if [ -z "$DESIRED_VERSION" ]; then
267
451
  echo ""
268
- echo -e "${YELLOW}!${NC} Could not read expected version from marketplace manifest."
269
- echo -e " ${DIM}Expected at: \$HOME/.claude/plugins/marketplaces/codevibe-marketplace/.claude-plugin/marketplace.json${NC}"
270
- echo -e " ${DIM}This usually means the marketplace refresh failed or the manifest schema has drifted.${NC}"
271
- echo -e " ${DIM}Try: claude plugin marketplace update codevibe-marketplace${NC}"
452
+ echo -e "${YELLOW}!${NC} Could not read the exact Claude plugin version required by this CodeVibe package."
272
453
  return 1
273
454
  fi
274
455
 
275
- echo -e " ${DIM}installed: ${INSTALLED_VERSION:-<none>} marketplace: ${EXPECTED_VERSION}${NC}"
456
+ # The 2.x package is distributed on @next and must use the matching private
457
+ # marketplace. Do not fall back to the public 1.x marketplace on this host.
458
+ if ! claude plugin marketplace update "$CLAUDE_MARKETPLACE" >/dev/null 2>&1; then
459
+ echo ""
460
+ echo -e "${YELLOW}!${NC} Could not refresh marketplace ${CLAUDE_MARKETPLACE}. No registration was changed."
461
+ return 1
462
+ fi
463
+ if ! MARKETPLACES=$(_capture_claude_marketplaces); then
464
+ echo ""
465
+ echo -e "${YELLOW}!${NC} Claude marketplace state is invalid or unavailable. No registration was changed."
466
+ return 1
467
+ fi
468
+ EXPECTED_VERSION=$(_read_marketplace_version "$CLAUDE_MARKETPLACE" "$MARKETPLACES") || EXPECTED_VERSION=""
469
+ if [ "$EXPECTED_VERSION" != "$DESIRED_VERSION" ]; then
470
+ echo ""
471
+ echo -e "${YELLOW}!${NC} Marketplace ${CLAUDE_MARKETPLACE} does not pin the required Claude plugin."
472
+ echo -e " ${DIM}Required: ${DESIRED_VERSION:-<none>} observed: ${EXPECTED_VERSION:-<none>}. No registration was changed.${NC}"
473
+ return 1
474
+ fi
475
+ if ! PLUGINS=$(_capture_claude_plugins); then
476
+ echo ""
477
+ echo -e "${YELLOW}!${NC} Claude plugin state is invalid or unavailable. No registration was changed."
478
+ return 1
479
+ fi
480
+ if PREVIEW_STATE=$(_read_claude_plugin_state "$CLAUDE_MARKETPLACE" "$PLUGINS"); then
481
+ PREVIEW_PRESENT=true
482
+ INSTALLED_VERSION=$(_claude_plugin_state_field "$PREVIEW_STATE" version) || return 1
483
+ INSTALLED_ENABLED=$(_claude_plugin_state_field "$PREVIEW_STATE" enabled) || return 1
484
+ INSTALLED_SCOPE=$(_claude_plugin_state_field "$PREVIEW_STATE" scope) || return 1
485
+ if [ "$INSTALLED_SCOPE" != "user" ]; then
486
+ echo ""
487
+ echo -e "${YELLOW}!${NC} Claude preview registration has unsupported scope: ${INSTALLED_SCOPE}. No registration was changed."
488
+ return 1
489
+ fi
490
+ else
491
+ [ "$?" -eq 3 ] || return 1
492
+ INSTALLED_VERSION=""
493
+ INSTALLED_ENABLED=false
494
+ INSTALLED_SCOPE=user
495
+ fi
496
+ echo -e " ${DIM}channel: ${CLAUDE_MARKETPLACE} installed: ${INSTALLED_VERSION:-<none>} required: ${EXPECTED_VERSION}${NC}"
497
+
498
+ # This is the only 2.x dogfood machine. Remove its public 1.x registration so
499
+ # Claude resume cannot start competing daemons from both plugin generations.
500
+ if CONFLICT_STATE=$(_read_claude_plugin_state "codevibe-marketplace" "$PLUGINS"); then
501
+ CONFLICT_PRESENT=true
502
+ CONFLICT_SCOPE=$(_claude_plugin_state_field "$CONFLICT_STATE" scope) || return 1
503
+ if [ "$CONFLICT_SCOPE" != "user" ]; then
504
+ echo ""
505
+ echo -e "${YELLOW}!${NC} Conflicting Claude registration has unsupported scope: ${CONFLICT_SCOPE}. No registration was changed."
506
+ return 1
507
+ fi
508
+ else
509
+ [ "$?" -eq 3 ] || return 1
510
+ fi
511
+ if [ "$CONFLICT_PRESENT" = "true" ]; then
512
+ if ! claude plugin uninstall -s "$CONFLICT_SCOPE" -y "codevibe-claude@codevibe-marketplace" >/dev/null 2>&1; then
513
+ echo ""
514
+ echo -e "${YELLOW}!${NC} Could not remove conflicting Claude plugin registration: codevibe-claude@codevibe-marketplace"
515
+ return 1
516
+ fi
517
+ REGISTRATION_CHANGED=true
518
+ fi
276
519
 
277
520
  # ─── Step 3: Decide if we need to force a cache-clearing reinstall ─────────
278
521
  local NEEDS_REINSTALL=false
279
- if [ -z "$INSTALLED_VERSION" ] || [ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]; then
522
+ if [ "$PREVIEW_PRESENT" != "true" ] || [ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]; then
280
523
  NEEDS_REINSTALL=true
281
524
  if [ -n "$INSTALLED_VERSION" ]; then
282
525
  echo -e "${YELLOW}!${NC} Cache is stale (${INSTALLED_VERSION} on disk, ${EXPECTED_VERSION} in marketplace)."
@@ -285,12 +528,14 @@ do_update() {
285
528
  fi
286
529
 
287
530
  if [ "$NEEDS_REINSTALL" = "true" ]; then
288
- # Must uninstall + nuke cache BEFORE install. `claude plugin install` will
289
- # silently reuse an existing cache dir even if the marketplace version changed.
290
- claude plugin uninstall codevibe-claude@codevibe-marketplace >/dev/null 2>&1 || true
291
- _nuke_claude_plugin_cache
531
+ if [ "$PREVIEW_PRESENT" = "true" ] \
532
+ && ! claude plugin uninstall -s "$INSTALLED_SCOPE" -y "codevibe-claude@$CLAUDE_MARKETPLACE" >/dev/null 2>&1; then
533
+ echo ""
534
+ echo -e "${YELLOW}!${NC} Could not remove stale Claude plugin registration: codevibe-claude@$CLAUDE_MARKETPLACE"
535
+ return 1
536
+ fi
292
537
 
293
- if claude plugin install codevibe-claude@codevibe-marketplace; then
538
+ if claude plugin install -s user -y "codevibe-claude@$CLAUDE_MARKETPLACE"; then
294
539
  echo -e "${GREEN}✓${NC} Claude plugin reinstalled"
295
540
  else
296
541
  echo ""
@@ -298,25 +543,70 @@ do_update() {
298
543
  return 1
299
544
  fi
300
545
  else
301
- # Version already matches. Still call install to make sure installed_plugins.json
302
- # is in sync, but treat any error as non-fatal since we're not actually changing state.
303
- claude plugin install codevibe-claude@codevibe-marketplace >/dev/null 2>&1 || true
546
+ # Version already matches. Re-enable the selected registration without
547
+ # asking Claude Code to reinstall or recache it.
548
+ if [ "$INSTALLED_ENABLED" != "true" ] \
549
+ && ! claude plugin enable -s "$INSTALLED_SCOPE" "codevibe-claude@$CLAUDE_MARKETPLACE" >/dev/null 2>&1; then
550
+ echo ""
551
+ echo -e "${YELLOW}!${NC} Could not enable Claude plugin registration: codevibe-claude@$CLAUDE_MARKETPLACE"
552
+ return 1
553
+ fi
304
554
  echo -e "${GREEN}✓${NC} Claude plugin already at ${INSTALLED_VERSION}"
305
555
  fi
306
556
 
307
557
  # ─── Step 4: Verify the installed version is now what we expected ──────────
308
558
  # EXPECTED_VERSION is guaranteed non-empty here (we bailed earlier otherwise),
309
559
  # so the verification is unconditional.
310
- INSTALLED_VERSION=$(_read_installed_version)
311
- if [ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]; then
560
+ if ! PLUGINS=$(_capture_claude_plugins); then
561
+ echo ""
562
+ echo -e "${YELLOW}!${NC} Claude plugin state could not be verified after the update."
563
+ return 1
564
+ fi
565
+ if ! PREVIEW_STATE=$(_read_claude_plugin_state "$CLAUDE_MARKETPLACE" "$PLUGINS"); then
566
+ echo ""
567
+ echo -e "${YELLOW}!${NC} Claude preview registration is missing after the update."
568
+ return 1
569
+ fi
570
+ INSTALLED_VERSION=$(_claude_plugin_state_field "$PREVIEW_STATE" version) || return 1
571
+ INSTALLED_ENABLED=$(_claude_plugin_state_field "$PREVIEW_STATE" enabled) || return 1
572
+ INSTALLED_SCOPE=$(_claude_plugin_state_field "$PREVIEW_STATE" scope) || return 1
573
+ if [ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ] || [ "$INSTALLED_SCOPE" != "user" ]; then
312
574
  echo ""
313
575
  echo -e "${YELLOW}!${NC} WARNING: Installed version (${INSTALLED_VERSION:-<none>}) still doesn't match expected (${EXPECTED_VERSION})."
314
- echo -e " ${DIM}Try: rm -rf ~/.claude/plugins/cache/codevibe-marketplace/codevibe-claude && codevibe update${NC}"
576
+ echo -e " ${DIM}Try: codevibe update again after checking the ${CLAUDE_MARKETPLACE} manifest.${NC}"
577
+ return 1
578
+ fi
579
+ if [ "$INSTALLED_ENABLED" != "true" ]; then
580
+ echo ""
581
+ echo -e "${YELLOW}!${NC} Claude plugin registration is installed but disabled: codevibe-claude@$CLAUDE_MARKETPLACE"
582
+ return 1
583
+ fi
584
+ local conflict_check_rc
585
+ _read_claude_plugin_state "codevibe-marketplace" "$PLUGINS" >/dev/null 2>&1
586
+ conflict_check_rc=$?
587
+ if [ "$conflict_check_rc" -eq 0 ]; then
588
+ echo ""
589
+ echo -e "${YELLOW}!${NC} Conflicting Claude plugin registration still exists: codevibe-claude@codevibe-marketplace"
590
+ return 1
591
+ fi
592
+ if [ "$conflict_check_rc" -ne 3 ]; then
593
+ echo ""
594
+ echo -e "${YELLOW}!${NC} Claude plugin state became ambiguous after the update."
595
+ return 1
596
+ fi
597
+ if ! _verify_only_preview_registration "$PLUGINS"; then
598
+ echo ""
599
+ echo -e "${YELLOW}!${NC} Claude plugin state contains an unexpected CodeVibe registration."
600
+ return 1
601
+ fi
602
+ if ! _verify_claude_plugin_files "$PREVIEW_STATE" "$EXPECTED_VERSION"; then
603
+ echo ""
604
+ echo -e "${YELLOW}!${NC} Claude plugin files do not match the required package identity."
315
605
  return 1
316
606
  fi
317
607
 
318
608
  # ─── Step 5: Tell the user about running sessions ──────────────────────────
319
- if [ "$NEEDS_REINSTALL" = "true" ]; then
609
+ if [ "$NEEDS_REINSTALL" = "true" ] || [ "$REGISTRATION_CHANGED" = "true" ]; then
320
610
  echo ""
321
611
  echo -e "${YELLOW}!${NC} Running Claude Code sessions still have the OLD plugin server in memory."
322
612
  echo -e " ${DIM}Exit and restart any active session to pick up the new plugin.${NC}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe",
3
- "version": "2.0.16",
3
+ "version": "2.0.17",
4
4
  "description": "Companion and orchestration for Claude Code, OpenAI Codex CLI, and Antigravity, with secure mobile control.",
5
5
  "bin": {
6
6
  "codevibe": "./bin/codevibe"