@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 +6 -6
- package/dist/cli.js +5 -4
- package/dist/credentials.d.ts +2 -0
- package/dist/credentials.js +62 -0
- package/dist/install.d.ts +1 -0
- package/dist/install.js +16 -7
- package/dist/server.js +1 -1
- package/package.json +1 -1
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.
|
|
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.
|
|
11
|
-
npx -y @hzlmy2002/web-market@0.1.
|
|
12
|
-
npx -y @hzlmy2002/web-market@0.1.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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,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
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.
|
|
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.
|
|
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 &&
|
|
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 &&
|
|
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
|
-
|
|
146
|
-
|
|
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.
|
|
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 {
|