tabctl 0.2.0 → 0.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
@@ -27,7 +27,7 @@ If you haven't run `npm link`, you can always use `node ./cli/tabctl.js` instead
27
27
 
28
28
  Run the interactive setup — it syncs the extension, tells you where to load it, and prompts for the extension ID:
29
29
 
30
- <!-- test: "setup interactive mode reads extension-id from stdin" -->
30
+ <!-- test: "setup explicit --extension-id overrides auto-derived ID" -->
31
31
  ```bash
32
32
  tabctl setup --browser chrome
33
33
  ```
@@ -194,10 +194,18 @@ async function runSetup(options, prettyOutput) {
194
194
  catch {
195
195
  extensionSync = null;
196
196
  }
197
- // Resolve extension ID: non-interactive if provided, interactive otherwise
197
+ // Resolve extension ID: explicit flag, derived from install path, or interactive prompt
198
198
  let extensionId = resolveExtensionId(options, false);
199
199
  if (!extensionId) {
200
- // Interactive mode
200
+ // Auto-derive from the installed extension path (Chromium uses SHA256 of the path)
201
+ const installedDir = (0, extension_sync_1.resolveInstalledExtensionDir)(config.baseDataDir);
202
+ if (fs_1.default.existsSync(path_1.default.join(installedDir, "manifest.json"))) {
203
+ extensionId = (0, extension_sync_1.deriveExtensionId)(installedDir);
204
+ process.stderr.write(`Extension ID derived from: ${installedDir}\n`);
205
+ }
206
+ }
207
+ if (!extensionId) {
208
+ // Interactive mode: sync hadn't happened or path doesn't exist
201
209
  if (extensionSync?.extensionDir) {
202
210
  process.stderr.write(`\nExtension synced to: ${extensionSync.extensionDir}\n`);
203
211
  try {
@@ -23,7 +23,7 @@ function printJson(payload, pretty = true) {
23
23
  function errorOut(message) {
24
24
  const hints = {
25
25
  "Unknown option: --format": "Use --json for JSON output. --format is only for report.",
26
- "ENOENT": "Native host not running. Ensure the browser extension is loaded and active.",
26
+ "ENOENT": "Native host not running. Ensure the browser extension is loaded and active. If you recently upgraded, run: tabctl setup",
27
27
  };
28
28
  const hint = Object.entries(hints).find(([key]) => message.includes(key))?.[1];
29
29
  if (hint) {
@@ -46,13 +46,13 @@ function setupStdoutErrorHandling() {
46
46
  function emitVersionWarnings(response, fallbackAction) {
47
47
  const hostVersion = typeof response.version === "string" ? response.version : null;
48
48
  if (hostVersion && hostVersion !== version_1.VERSION) {
49
- process.stderr.write(`[tabctl] version mismatch: cli ${version_1.VERSION}, host ${hostVersion}\n`);
49
+ process.stderr.write(`[tabctl] version mismatch: cli ${version_1.VERSION}, host ${hostVersion}. Run: tabctl setup\n`);
50
50
  }
51
51
  const data = response.data;
52
52
  const extensionVersion = data && typeof data.extensionVersion === "string" ? data.extensionVersion : null;
53
53
  const extensionComponent = data && typeof data.extensionComponent === "string" ? data.extensionComponent : null;
54
54
  if (extensionVersion && hostVersion && extensionVersion !== hostVersion) {
55
- process.stderr.write(`[tabctl] version mismatch: host ${hostVersion}, extension ${extensionVersion}\n`);
55
+ process.stderr.write(`[tabctl] version mismatch: host ${hostVersion}, extension ${extensionVersion}. Reload the extension in your browser\n`);
56
56
  }
57
57
  if (extensionComponent && extensionComponent !== "extension") {
58
58
  process.stderr.write(`[tabctl] unexpected extension component: ${extensionComponent}\n`);
@@ -60,6 +60,6 @@ function emitVersionWarnings(response, fallbackAction) {
60
60
  const action = response.action || fallbackAction;
61
61
  const extensionExpected = !["history", "version"].includes(action);
62
62
  if (extensionExpected && !extensionVersion) {
63
- process.stderr.write("[tabctl] extension version unavailable; reload the extension to validate version match\n");
63
+ process.stderr.write("[tabctl] extension version unavailable. Reload the extension in your browser\n");
64
64
  }
65
65
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Tab Control",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "Archive and manage browser tabs with CLI support",
6
6
  "permissions": [
7
7
  "tabs",
@@ -19,5 +19,5 @@
19
19
  "background": {
20
20
  "service_worker": "background.js"
21
21
  },
22
- "version_name": "0.2.0"
22
+ "version_name": "0.3.0"
23
23
  }
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.EXTENSION_DIR_NAME = void 0;
7
+ exports.deriveExtensionId = deriveExtensionId;
7
8
  exports.resolveBundledExtensionDir = resolveBundledExtensionDir;
8
9
  exports.resolveInstalledExtensionDir = resolveInstalledExtensionDir;
9
10
  exports.readExtensionVersion = readExtensionVersion;
@@ -11,8 +12,17 @@ exports.syncExtension = syncExtension;
11
12
  exports.checkExtensionSync = checkExtensionSync;
12
13
  const path_1 = __importDefault(require("path"));
13
14
  const fs_1 = __importDefault(require("fs"));
15
+ const crypto_1 = __importDefault(require("crypto"));
14
16
  const config_1 = require("./config");
15
17
  exports.EXTENSION_DIR_NAME = "extension";
18
+ /**
19
+ * Derive the Chrome/Edge extension ID for an unpacked extension path.
20
+ * Chromium computes: SHA256(absolute_path) → first 32 hex chars → map 0-f to a-p.
21
+ */
22
+ function deriveExtensionId(extensionDir) {
23
+ const hash = crypto_1.default.createHash("sha256").update(extensionDir).digest("hex").slice(0, 32);
24
+ return hash.split("").map(c => String.fromCharCode("a".charCodeAt(0) + parseInt(c, 16))).join("");
25
+ }
16
26
  function resolveBundledExtensionDir() {
17
27
  const dir = path_1.default.resolve(__dirname, "../extension");
18
28
  const manifest = path_1.default.join(dir, "manifest.json");
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DIRTY = exports.GIT_SHA = exports.VERSION = exports.BASE_VERSION = void 0;
4
- exports.BASE_VERSION = "0.2.0";
5
- exports.VERSION = "0.2.0";
4
+ exports.BASE_VERSION = "0.3.0";
5
+ exports.VERSION = "0.3.0";
6
6
  exports.GIT_SHA = null;
7
7
  exports.DIRTY = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tabctl",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI tool to manage and analyze browser tabs",
5
5
  "license": "MIT",
6
6
  "repository": {