@hzlmy2002/web-market 0.1.0 → 0.1.1

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
@@ -4,15 +4,15 @@ Node/TypeScript stdio MCP for focused website competitive analysis. Five tools c
4
4
 
5
5
  ## Install with npx
6
6
 
7
- Requires Node 22 or newer. Set AISA_API_KEY in the environment of the process launching your MCP client, then run one of:
7
+ Requires Node 22 or newer. Run one of these commands in a terminal:
8
8
 
9
9
  ```sh
10
- npx -y @hzlmy2002/web-market@0.1.0 setup --client codex
11
- npx -y @hzlmy2002/web-market@0.1.0 setup --client claude-code
12
- npx -y @hzlmy2002/web-market@0.1.0 setup --client hermes
10
+ npx -y @hzlmy2002/web-market@0.1.1 setup --client codex
11
+ npx -y @hzlmy2002/web-market@0.1.1 setup --client claude-code
12
+ npx -y @hzlmy2002/web-market@0.1.1 setup --client hermes
13
13
  ```
14
14
 
15
- This installs the Skill and registers a version-pinned `npx -y @hzlmy2002/web-market@0.1.0 serve` MCP command. Clearing the npm cache does not invalidate the configured path: npx downloads the pinned release again when necessary. Restart or refresh the client to discover the Skill. Credentials must still reach the MCP process; the installer never stores your key. Use `uninstall --client ...` to remove an unchanged managed installation.
15
+ This installs the Skill and registers a version-pinned `npx -y @hzlmy2002/web-market@0.1.1 serve` MCP command. Clearing the npm cache does not invalidate the configured path: npx downloads the pinned release again when necessary. Restart or refresh the client to discover the Skill. If no key is configured, setup prompts for hidden input and saves the key in the selected client’s MCP `env.AISA_API_KEY` setting. This works independently of shell startup files on macOS, Linux, and Windows. Existing saved keys are reused. If `AISA_API_KEY` is already set in the setup environment, setup keeps using environment-based authentication without copying the key; the client must inherit that variable. Non-interactive setup without a saved or environment key exits with instructions. Use `uninstall --client ...` to remove an unchanged managed installation.
16
16
 
17
17
  ## Local setup
18
18
 
@@ -24,7 +24,7 @@ npm test
24
24
  npm run build
25
25
  ```
26
26
 
27
- Set `AISA_API_KEY` in the environment of the process launching your MCP client. It is an AIsa API key; never put it in a Skill or commit it. Desktop clients started outside your shell may need the variable configured explicitly in their MCP settings. The installer does not read or persist the key.
27
+ Setup prompts for a missing AIsa API key and stores it as plaintext in the selected client configuration, with restrictive file permissions where supported. It is never printed or copied into Skills or installer state. This is a temporary Bearer authentication flow ahead of OAuth support. Setup does not edit shell profiles or Windows user environment variables. Alternatively, set `AISA_API_KEY` in the environment inherited by your MCP client. Desktop applications may not inherit variables exported in a terminal.
28
28
 
29
29
  Run one of these from this checkout:
30
30
 
package/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { resolveSetupKey } from './credentials.js';
2
3
  import { parseArgs } from 'node:util';
3
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
5
  import { createServer } from './server.js';
@@ -7,20 +8,20 @@ async function main() {
7
8
  const { values, positionals } = parseArgs({ allowPositionals: true, options: { client: { type: 'string' }, home: { type: 'string' }, 'install-skills': { type: 'boolean' }, help: { type: 'boolean' } } });
8
9
  const command = positionals[0] ?? 'serve';
9
10
  if (values.help) {
10
- console.log('aisa-web-market serve [--install-skills --client codex|claude-code|hermes]\naisa-web-market setup|uninstall --client codex|claude-code|hermes [--home directory]\naisa-web-market status\nSet AISA_API_KEY in the server environment. setup never stores the token.');
11
+ console.log('aisa-web-market serve [--install-skills --client codex|claude-code|hermes]\naisa-web-market setup|uninstall --client codex|claude-code|hermes [--home directory]\naisa-web-market status\nSet AISA_API_KEY in the server environment. setup prompts for a missing key and saves it in the client MCP configuration.');
11
12
  return;
12
13
  }
13
14
  if (positionals.length > 1)
14
15
  throw Error('Unexpected positional argument.');
15
16
  if (command === 'status') {
16
- console.log(JSON.stringify({ credential_configured: Boolean(process.env.AISA_API_KEY?.trim()), authentication: 'bearer', version: '0.1.0' }));
17
+ console.log(JSON.stringify({ credential_configured: Boolean(process.env.AISA_API_KEY?.trim()), authentication: 'bearer', version: '0.1.1' }));
17
18
  return;
18
19
  }
19
20
  if (command === 'setup' || command === 'uninstall') {
20
21
  if (!values.client)
21
22
  throw Error('--client is required.');
22
- console.log(JSON.stringify(await install(values.client, { home: values.home, remove: command === 'uninstall' })));
23
- console.log('Client configuration preserves existing values; serialization may reformat comments. Ensure AISA_API_KEY reaches the client process. Restart or refresh the client to load skills.');
23
+ console.log(JSON.stringify(await install(values.client, { home: values.home, remove: command === 'uninstall', resolveKey: command === 'setup' ? resolveSetupKey : undefined })));
24
+ console.log('Client configuration preserves existing values; serialization may reformat comments. A saved key is passed directly to the MCP process; otherwise ensure the client inherits AISA_API_KEY. Restart or refresh the client to load skills.');
24
25
  return;
25
26
  }
26
27
  if (command !== 'serve')
@@ -0,0 +1,2 @@
1
+ export declare function promptKey(): Promise<string>;
2
+ export declare function resolveSetupKey(savedKey?: string): Promise<string | undefined>;
@@ -0,0 +1,62 @@
1
+ import { emitKeypressEvents } from 'node:readline';
2
+ // Read from the terminal without echoing credentials or consuming MCP stdin.
3
+ export async function promptKey() {
4
+ if (!process.stdin.isTTY || !process.stderr.isTTY) {
5
+ throw Error('AISA_API_KEY is missing. Run setup in an interactive terminal to save a key, or set AISA_API_KEY before running setup.');
6
+ }
7
+ process.stderr.write('Enter AISA_API_KEY (saved locally in the client MCP configuration; input hidden): ');
8
+ const input = process.stdin;
9
+ const wasRaw = input.isRaw;
10
+ emitKeypressEvents(input);
11
+ input.setRawMode(true);
12
+ input.resume();
13
+ return new Promise((resolve, reject) => {
14
+ let value = '';
15
+ const finish = (error) => {
16
+ input.removeListener('keypress', onKey);
17
+ input.removeListener('end', onEnd);
18
+ input.removeListener('error', onError);
19
+ input.setRawMode(wasRaw);
20
+ input.pause();
21
+ process.stderr.write('\n');
22
+ if (error)
23
+ reject(error);
24
+ else
25
+ resolve(value.trim());
26
+ };
27
+ const onEnd = () => finish(Error('Key input ended; setup cancelled.'));
28
+ const onError = () => finish(Error('Could not read key; setup cancelled.'));
29
+ const onKey = (text, key) => {
30
+ if (key.ctrl && (key.name === 'c' || key.name === 'd'))
31
+ return finish(Error('Setup cancelled.'));
32
+ if (key.name === 'return' || key.name === 'enter') {
33
+ if (!value.trim()) {
34
+ process.stderr.write('\nKey cannot be empty. Enter AISA_API_KEY: ');
35
+ return;
36
+ }
37
+ return finish();
38
+ }
39
+ if (key.name === 'backspace') {
40
+ value = Array.from(value).slice(0, -1).join('');
41
+ return;
42
+ }
43
+ if (key.ctrl && key.name === 'u') {
44
+ value = '';
45
+ return;
46
+ }
47
+ if (!key.ctrl && !key.meta && text && !/[\x00-\x1f\x7f]/.test(text))
48
+ value += text;
49
+ };
50
+ input.on('keypress', onKey);
51
+ input.once('end', onEnd);
52
+ input.once('error', onError);
53
+ });
54
+ }
55
+ export async function resolveSetupKey(savedKey) {
56
+ if (savedKey?.trim())
57
+ return savedKey;
58
+ // Preserve the existing environment-based setup; only prompted keys are saved.
59
+ if (process.env.AISA_API_KEY?.trim())
60
+ return undefined;
61
+ return promptKey();
62
+ }
package/dist/install.d.ts CHANGED
@@ -9,6 +9,7 @@ export declare function install(client: Client, options?: {
9
9
  home?: string;
10
10
  remove?: boolean;
11
11
  skillsOnly?: boolean;
12
+ resolveKey?: (savedKey?: string) => Promise<string | undefined>;
12
13
  }): Promise<{
13
14
  client: Client;
14
15
  skill: string;
package/dist/install.js CHANGED
@@ -12,7 +12,7 @@ const skillName = 'aisa-web-market';
12
12
  // local Node entry so development changes remain immediately testable.
13
13
  export function launchEntry(client, root = packageRoot) {
14
14
  const command = root.split(path.sep).includes('node_modules')
15
- ? { command: process.platform === 'win32' ? 'npx.cmd' : 'npx', args: ['-y', '@hzlmy2002/web-market@0.1.0', 'serve'] }
15
+ ? { command: process.platform === 'win32' ? 'npx.cmd' : 'npx', args: ['-y', '@hzlmy2002/web-market@0.1.1', 'serve'] }
16
16
  : { command: process.execPath, args: [path.join(root, 'dist/cli.js'), 'serve'] };
17
17
  return { ...command, ...(client === 'codex' ? { env_vars: ['AISA_API_KEY'] } : {}) };
18
18
  }
@@ -96,7 +96,7 @@ export async function install(client, options = {}) {
96
96
  const root = skillRoot(client, home);
97
97
  const entry = launchEntry(client);
98
98
  const writes = new Map();
99
- const next = { version: '0.1.0', files: {}, config: previous.config };
99
+ const next = { version: '0.1.1', files: {}, config: previous.config, configHash: previous.configHash };
100
100
  const bundled = await skillFiles(path.join(packageRoot, 'skills', skillName));
101
101
  for (const relative of new Set([...Object.keys(bundled), ...Object.keys(previous.files)])) {
102
102
  if (path.isAbsolute(relative) || relative.split(/[\\/]/).includes('..'))
@@ -118,7 +118,7 @@ export async function install(client, options = {}) {
118
118
  next.files[relative] = hash(content);
119
119
  }
120
120
  }
121
- if (!options.skillsOnly && (!options.remove || previous.config)) {
121
+ if (!options.skillsOnly && (!options.remove || previous.config || previous.configHash)) {
122
122
  await noSymlinks(cfg.file);
123
123
  const current = await read(cfg.file);
124
124
  let config;
@@ -134,16 +134,25 @@ export async function install(client, options = {}) {
134
134
  if (typeof config[cfg.key] !== 'object' || Array.isArray(config[cfg.key]))
135
135
  throw Error('Invalid MCP configuration section.');
136
136
  const existing = config[cfg.key][skillName];
137
+ const owned = previous.configHash ? existing !== undefined && hash(JSON.stringify(existing)) === previous.configHash : JSON.stringify(existing) === JSON.stringify(previous.config);
137
138
  if (options.remove) {
138
- if (existing && JSON.stringify(existing) !== JSON.stringify(previous.config))
139
+ if (existing && !owned)
139
140
  throw Error('MCP entry has been modified; preserving it.');
140
141
  delete config[cfg.key][skillName];
141
142
  }
142
143
  else {
143
- if (existing && JSON.stringify(existing) !== JSON.stringify(previous.config) && JSON.stringify(existing) !== JSON.stringify(entry))
144
+ if (existing && !owned && JSON.stringify(existing) !== JSON.stringify(entry))
144
145
  throw Error('An unowned aisa-web-market MCP entry already exists; preserving it.');
145
- config[cfg.key][skillName] = entry;
146
- next.config = entry;
146
+ const savedKey = typeof existing?.env?.AISA_API_KEY === 'string' ? existing.env.AISA_API_KEY : undefined;
147
+ const apiKey = options.resolveKey ? await options.resolveKey(savedKey) : savedKey;
148
+ const configured = { ...entry };
149
+ if (apiKey) {
150
+ configured.env = { AISA_API_KEY: apiKey };
151
+ delete configured.env_vars;
152
+ }
153
+ config[cfg.key][skillName] = configured;
154
+ delete next.config;
155
+ next.configHash = hash(JSON.stringify(configured));
147
156
  }
148
157
  writes.set(cfg.file, cfg.stringify(config));
149
158
  }
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@ import { ApiClient, safeError } from './api.js';
3
3
  import { inputs, output } from './schema.js';
4
4
  import { execute, descriptions } from './tools.js';
5
5
  export function createServer(api = new ApiClient()) {
6
- const server = new McpServer({ name: 'aisa-web-market', version: '0.1.0' }, { instructions: 'Website competitive analysis using five AIsa tools. Use the installed aisa-web-market skill for scenario guidance. Compare identical scope and preserve missing-data evidence. API calls are billed. Credentials come from AISA_API_KEY; never request the key in conversation.' });
6
+ const server = new McpServer({ name: 'aisa-web-market', version: '0.1.1' }, { instructions: 'Website competitive analysis using five AIsa tools. Use the installed aisa-web-market skill for scenario guidance. Compare identical scope and preserve missing-data evidence. API calls are billed. Credentials come from AISA_API_KEY; never request the key in conversation.' });
7
7
  for (const name of Object.keys(inputs)) {
8
8
  server.registerTool(name, { description: descriptions[name], inputSchema: inputs[name], outputSchema: output.shape, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true } }, async (args, extra) => {
9
9
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hzlmy2002/web-market",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Focused website competitive analysis: five MCP tools and a portable skill",
5
5
  "type": "module",
6
6
  "bin": {