clishop 1.2.4 → 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
@@ -31,12 +31,12 @@ CLISHOP is an open-source CLI that lets AI agents and humans search for products
31
31
 
32
32
  ## Highlights
33
33
 
34
- - **Multi-store search:** one query searches every store in the network and only returns items that ship to your address
35
- - **Safety thresholds:** cap how much can be spent per order, require email confirmation before anything ships, or skip confirmation entirely
36
- - **MCP server:** native [Model Context Protocol](https://modelcontextprotocol.io/) support with 19 tools so AI agents (VS Code Copilot, Claude, Cursor, Windsurf, etc.) can shop on your behalf
37
- - **Advertise requests:** can't find what you need? Publicly post a request and let stores try to find it for you
38
- - **Support & reviews:** if something goes wrong, create support tickets and handle the entire process from the CLI — write product and store reviews too
39
- - **Open marketplace:** anyone can start selling on CLISHOP by deploying a [Dark Store](https://github.com/DavooxBv2/CLISHOP-DARKSTORE) no website required
34
+ - One query searches every store in the network. Results are filtered to what actually ships to your address.
35
+ - Set spending caps per order, require email confirmation before anything ships, or let it go through automatically — your call.
36
+ - Ships as a native [MCP server](https://modelcontextprotocol.io/) with 44 tools. Works with VS Code Copilot, Claude, Cursor, Windsurf, and anything else that speaks MCP.
37
+ - Can't find what you need? Post an advertise request and let vendors compete to fulfill it.
38
+ - Support tickets, product reviews, store reviews all from the terminal.
39
+ - Anyone can sell on CLISHOP by deploying a [Dark Store](https://github.com/DavooxBv2/CLISHOP-DARKSTORE). No website needed.
40
40
 
41
41
  ---
42
42
 
@@ -132,7 +132,7 @@ clishop buy 1
132
132
 
133
133
  ## Sell on CLISHOP
134
134
 
135
- Want to sell your own products? Use the [Dark Store](https://github.com/DavooxBv2/CLISHOP-DARKSTORE) template to create your own store — no website needed. Configure your catalog, shipping, and pricing in a few YAML files, deploy to Vercel, and start receiving orders.
135
+ You can run your own store with the [Dark Store](https://github.com/DavooxBv2/CLISHOP-DARKSTORE) template. Define your catalog, shipping rules, and pricing in YAML, deploy to Vercel, and you're live. No website needed.
136
136
 
137
137
  ---
138
138
 
@@ -150,7 +150,7 @@ npm run lint # Type-check
150
150
 
151
151
  ## MCP Server
152
152
 
153
- CLISHOP ships as a native MCP server with 19 tools. Any MCP-compatible client gets shopping capabilities out of the box.
153
+ CLISHOP ships as a native MCP server with 44 tools. Any MCP-compatible client gets shopping capabilities out of the box.
154
154
 
155
155
  ```bash
156
156
  clishop-mcp # If installed globally
@@ -3,12 +3,116 @@ import {
3
3
  } from "./chunk-X3H7SYR4.js";
4
4
 
5
5
  // src/auth.ts
6
- import keytar from "keytar";
6
+ import { createRequire } from "module";
7
7
  import axios from "axios";
8
+
9
+ // src/auth-file-store.ts
10
+ import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
11
+ import { join } from "path";
12
+ import { homedir } from "os";
13
+ var CONFIG_DIR = join(homedir(), ".config", "clishop");
14
+ var AUTH_FILE = join(CONFIG_DIR, "auth.json");
15
+ function ensureDir() {
16
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
17
+ }
18
+ function readStore() {
19
+ try {
20
+ return JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
21
+ } catch {
22
+ return {};
23
+ }
24
+ }
25
+ function writeStore(data) {
26
+ ensureDir();
27
+ writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
28
+ try {
29
+ chmodSync(AUTH_FILE, 384);
30
+ } catch {
31
+ }
32
+ }
33
+ function fileSetPassword(service, account, password) {
34
+ const store = readStore();
35
+ store[`${service}:${account}`] = password;
36
+ writeStore(store);
37
+ }
38
+ function fileGetPassword(service, account) {
39
+ const store = readStore();
40
+ return store[`${service}:${account}`] ?? null;
41
+ }
42
+ function fileDeletePassword(service, account) {
43
+ const store = readStore();
44
+ delete store[`${service}:${account}`];
45
+ writeStore(store);
46
+ }
47
+
48
+ // src/auth.ts
49
+ var require2 = createRequire(import.meta.url);
50
+ var _keytar = null;
51
+ var _keytarChecked = false;
52
+ function getKeytar() {
53
+ if (_keytarChecked) return _keytar;
54
+ _keytarChecked = true;
55
+ try {
56
+ _keytar = require2("keytar");
57
+ } catch {
58
+ _keytar = null;
59
+ }
60
+ return _keytar;
61
+ }
8
62
  var SERVICE_NAME = "clishop";
9
63
  var ACCOUNT_TOKEN = "auth-token";
10
64
  var ACCOUNT_REFRESH = "refresh-token";
11
65
  var ACCOUNT_USER = "user-info";
66
+ var _activeBackend = null;
67
+ function resolveBackend() {
68
+ if (_activeBackend) return _activeBackend;
69
+ if (process.env.CLISHOP_TOKEN) {
70
+ _activeBackend = "env";
71
+ return _activeBackend;
72
+ }
73
+ const kt = getKeytar();
74
+ if (kt) {
75
+ _activeBackend = "keytar";
76
+ return _activeBackend;
77
+ }
78
+ if (!process.env.CLISHOP_QUIET) {
79
+ console.warn(
80
+ "[clishop] Secure keychain unavailable \u2014 using file-based token storage (~/.config/clishop/auth.json).\n To enable keychain on Ubuntu/WSL: sudo apt install libsecret-1-0\n"
81
+ );
82
+ }
83
+ _activeBackend = "file";
84
+ return _activeBackend;
85
+ }
86
+ function isKeytarAvailable() {
87
+ return getKeytar() !== null;
88
+ }
89
+ async function setPassword(account, value) {
90
+ const backend = resolveBackend();
91
+ if (backend === "env") return;
92
+ if (backend === "keytar") {
93
+ return getKeytar().setPassword(SERVICE_NAME, account, value);
94
+ }
95
+ fileSetPassword(SERVICE_NAME, account, value);
96
+ }
97
+ async function getPassword(account) {
98
+ const backend = resolveBackend();
99
+ if (backend === "env" && account === ACCOUNT_TOKEN) {
100
+ return process.env.CLISHOP_TOKEN;
101
+ }
102
+ if (backend === "keytar") {
103
+ return getKeytar().getPassword(SERVICE_NAME, account);
104
+ }
105
+ return fileGetPassword(SERVICE_NAME, account);
106
+ }
107
+ async function deletePassword(account) {
108
+ const backend = resolveBackend();
109
+ if (backend === "env") return;
110
+ if (backend === "keytar") {
111
+ await getKeytar().deletePassword(SERVICE_NAME, account);
112
+ return;
113
+ }
114
+ fileDeletePassword(SERVICE_NAME, account);
115
+ }
12
116
  function assertAuthResponse(data) {
13
117
  const payload = data;
14
118
  if (!payload || typeof payload !== "object") {
@@ -29,22 +133,22 @@ function assertAuthResponse(data) {
29
133
  return payload;
30
134
  }
31
135
  async function storeToken(token) {
32
- await keytar.setPassword(SERVICE_NAME, ACCOUNT_TOKEN, token);
136
+ await setPassword(ACCOUNT_TOKEN, token);
33
137
  }
34
138
  async function storeRefreshToken(token) {
35
- await keytar.setPassword(SERVICE_NAME, ACCOUNT_REFRESH, token);
139
+ await setPassword(ACCOUNT_REFRESH, token);
36
140
  }
37
141
  async function storeUserInfo(user) {
38
- await keytar.setPassword(SERVICE_NAME, ACCOUNT_USER, JSON.stringify(user));
142
+ await setPassword(ACCOUNT_USER, JSON.stringify(user));
39
143
  }
40
144
  async function getToken() {
41
- return keytar.getPassword(SERVICE_NAME, ACCOUNT_TOKEN);
145
+ return getPassword(ACCOUNT_TOKEN);
42
146
  }
43
147
  async function getRefreshToken() {
44
- return keytar.getPassword(SERVICE_NAME, ACCOUNT_REFRESH);
148
+ return getPassword(ACCOUNT_REFRESH);
45
149
  }
46
150
  async function getUserInfo() {
47
- const raw = await keytar.getPassword(SERVICE_NAME, ACCOUNT_USER);
151
+ const raw = await getPassword(ACCOUNT_USER);
48
152
  if (!raw) return null;
49
153
  try {
50
154
  return JSON.parse(raw);
@@ -53,13 +157,12 @@ async function getUserInfo() {
53
157
  }
54
158
  }
55
159
  async function clearAuth() {
56
- await keytar.deletePassword(SERVICE_NAME, ACCOUNT_TOKEN);
57
- await keytar.deletePassword(SERVICE_NAME, ACCOUNT_REFRESH);
58
- await keytar.deletePassword(SERVICE_NAME, ACCOUNT_USER);
160
+ await deletePassword(ACCOUNT_TOKEN);
161
+ await deletePassword(ACCOUNT_REFRESH);
162
+ await deletePassword(ACCOUNT_USER);
59
163
  }
60
164
  async function isLoggedIn() {
61
- const token = await getToken();
62
- return !!token;
165
+ return !!await getToken();
63
166
  }
64
167
  async function login(email, password) {
65
168
  const baseUrl = getApiBaseUrl();
@@ -198,6 +301,9 @@ function handleApiError(error) {
198
301
  }
199
302
 
200
303
  export {
304
+ resolveBackend,
305
+ isKeytarAvailable,
306
+ getToken,
201
307
  getUserInfo,
202
308
  isLoggedIn,
203
309
  login,
package/dist/index.js CHANGED
@@ -2,18 +2,22 @@
2
2
  import {
3
3
  ensureAgentOnBackend,
4
4
  getApiClient,
5
+ getToken,
5
6
  getUserInfo,
6
7
  handleApiError,
8
+ isKeytarAvailable,
7
9
  isLoggedIn,
8
10
  login,
9
11
  logout,
10
- register
11
- } from "./chunk-CVK6G342.js";
12
+ register,
13
+ resolveBackend
14
+ } from "./chunk-3BBLDX6L.js";
12
15
  import {
13
16
  createAgent,
14
17
  deleteAgent,
15
18
  getActiveAgent,
16
19
  getAgent,
20
+ getApiBaseUrl,
17
21
  getConfig,
18
22
  listAgents,
19
23
  setActiveAgent,
@@ -22,7 +26,7 @@ import {
22
26
 
23
27
  // src/index.ts
24
28
  import { Command } from "commander";
25
- import chalk15 from "chalk";
29
+ import chalk16 from "chalk";
26
30
 
27
31
  // src/commands/auth.ts
28
32
  import chalk from "chalk";
@@ -932,7 +936,7 @@ async function runSetupWizard() {
932
936
  console.log();
933
937
  console.log(chalk4.bold.cyan(" W E L C O M E T O C L I S H O P"));
934
938
  console.log(chalk4.dim(" Order anything from your terminal."));
935
- console.log(chalk4.dim(` Build: ${"2026-03-04T15:39:11.106Z"}`));
939
+ console.log(chalk4.dim(` Build: ${"2026-03-22T16:08:08.167Z"}`));
936
940
  console.log();
937
941
  divider(chalk4.cyan);
938
942
  console.log();
@@ -4580,16 +4584,74 @@ function registerFeedbackCommands(program2) {
4580
4584
  });
4581
4585
  }
4582
4586
 
4587
+ // src/commands/doctor.ts
4588
+ import chalk15 from "chalk";
4589
+ import { join } from "path";
4590
+ import { homedir } from "os";
4591
+ import { mkdirSync } from "fs";
4592
+ import axios from "axios";
4593
+ function registerDoctorCommand(program2) {
4594
+ program2.command("doctor").description("Check system compatibility and auth status").action(async () => {
4595
+ const checks = [];
4596
+ const keytarOk = isKeytarAvailable();
4597
+ checks.push({
4598
+ name: "Secure keychain (keytar)",
4599
+ ok: keytarOk,
4600
+ detail: keytarOk ? "keytar loaded successfully" : "keytar unavailable \u2014 install libsecret:\n sudo apt install libsecret-1-0"
4601
+ });
4602
+ const backend = resolveBackend();
4603
+ checks.push({
4604
+ name: "Auth backend",
4605
+ ok: true,
4606
+ detail: backend === "env" ? "Using CLISHOP_TOKEN environment variable" : backend === "keytar" ? "Using OS keychain" : "Using file store (~/.config/clishop/auth.json)"
4607
+ });
4608
+ try {
4609
+ const dir = join(homedir(), ".config", "clishop");
4610
+ mkdirSync(dir, { recursive: true, mode: 448 });
4611
+ checks.push({ name: "File store writable", ok: true, detail: dir });
4612
+ } catch (e) {
4613
+ checks.push({
4614
+ name: "File store writable",
4615
+ ok: false,
4616
+ detail: e instanceof Error ? e.message : String(e)
4617
+ });
4618
+ }
4619
+ const token = await getToken();
4620
+ checks.push({
4621
+ name: "Authenticated",
4622
+ ok: !!token,
4623
+ detail: token ? "Token present" : "Not logged in \u2014 run: clishop setup"
4624
+ });
4625
+ const apiUrl = getApiBaseUrl();
4626
+ try {
4627
+ await axios.get(`${apiUrl}/health`, { timeout: 5e3 });
4628
+ checks.push({ name: "API reachable", ok: true, detail: apiUrl });
4629
+ } catch {
4630
+ checks.push({
4631
+ name: "API reachable",
4632
+ ok: false,
4633
+ detail: `Cannot reach ${apiUrl}`
4634
+ });
4635
+ }
4636
+ console.log(chalk15.bold("\n CLISHOP Doctor\n"));
4637
+ for (const c of checks) {
4638
+ const icon = c.ok ? chalk15.green("\u2713") : chalk15.red("\u2717");
4639
+ console.log(` ${icon} ${chalk15.bold(c.name)}: ${c.detail}`);
4640
+ }
4641
+ console.log();
4642
+ });
4643
+ }
4644
+
4583
4645
  // src/index.ts
4584
4646
  var program = new Command();
4585
- program.name("clishop").version("1.2.3").description(
4586
- chalk15.bold("CLISHOP") + ' \u2014 Order anything from your terminal.\n\n Use agents to set safety limits, addresses, and payment methods.\n The "default" agent is used when no agent is specified.'
4647
+ program.name("clishop").version("1.3.0").description(
4648
+ chalk16.bold("CLISHOP") + ' \u2014 Order anything from your terminal.\n\n Use agents to set safety limits, addresses, and payment methods.\n The "default" agent is used when no agent is specified.'
4587
4649
  ).option("--agent <name>", "Use a specific agent for this command").hook("preAction", (thisCommand) => {
4588
4650
  const agentOpt = thisCommand.opts().agent;
4589
4651
  if (agentOpt) {
4590
4652
  const config = getConfig();
4591
4653
  if (!config.store.agents[agentOpt]) {
4592
- console.error(chalk15.red(`\u2717 Agent "${agentOpt}" does not exist.`));
4654
+ console.error(chalk16.red(`\u2717 Agent "${agentOpt}" does not exist.`));
4593
4655
  process.exit(1);
4594
4656
  }
4595
4657
  process.env.__CLISHOP_AGENT_OVERRIDE = agentOpt;
@@ -4609,6 +4671,7 @@ registerSetupCommand(program);
4609
4671
  registerAdvertiseCommands(program);
4610
4672
  registerSupportCommands(program);
4611
4673
  registerFeedbackCommands(program);
4674
+ registerDoctorCommand(program);
4612
4675
  async function main() {
4613
4676
  if (process.argv.includes("--mcp")) {
4614
4677
  const { startMcpServer } = await import("./mcp.js");
@@ -4626,6 +4689,6 @@ async function main() {
4626
4689
  await program.parseAsync(process.argv);
4627
4690
  }
4628
4691
  main().catch((err) => {
4629
- console.error(chalk15.red(err.message));
4692
+ console.error(chalk16.red(err.message));
4630
4693
  process.exit(1);
4631
4694
  });
package/dist/mcp.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  getApiClient,
4
4
  getUserInfo,
5
5
  isLoggedIn
6
- } from "./chunk-CVK6G342.js";
6
+ } from "./chunk-3BBLDX6L.js";
7
7
  import {
8
8
  __export,
9
9
  getActiveAgent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clishop",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "mcpName": "io.github.StefDCL/clishop",
5
5
  "description": "CLISHOP — Order anything from your terminal",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,8 @@
17
17
  "dev:mcp": "tsx src/mcp.ts",
18
18
  "start": "node dist/index.js",
19
19
  "start:mcp": "node dist/mcp.js",
20
- "lint": "tsc --noEmit"
20
+ "lint": "tsc --noEmit",
21
+ "postinstall": "node -e \"try{require('keytar')}catch{console.warn('\\n[clishop] Optional: install libsecret for secure keychain storage:\\n sudo apt install libsecret-1-0\\nWithout it, clishop uses file-based token storage.\\n')}\""
21
22
  },
22
23
  "repository": {
23
24
  "type": "git",
@@ -49,10 +50,12 @@
49
50
  "commander": "^14.0.3",
50
51
  "conf": "^15.1.0",
51
52
  "inquirer": "^13.2.4",
52
- "keytar": "^7.9.0",
53
53
  "open": "^11.0.0",
54
54
  "ora": "^9.3.0"
55
55
  },
56
+ "optionalDependencies": {
57
+ "keytar": "^7.9.0"
58
+ },
56
59
  "devDependencies": {
57
60
  "@types/inquirer": "^9.0.9",
58
61
  "@types/keytar": "^4.4.0",