apiskill 0.1.4 → 0.1.5

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
@@ -17,7 +17,7 @@ npm install -g apiskill
17
17
  apiskill run web
18
18
  ```
19
19
 
20
- Global npm installations store version data in the user-writable `~/.apiskill/cache` directory by default. Use `--cwd` for a project-local `<cwd>/cache`, or `--cache-dir` for a custom location. When developing this repository, you can also start it from the project root:
20
+ Web, CLI, and MCP all use the same user-writable `~/.apiskill/cache` directory by default. `--cwd` only changes the Web process working directory and never changes this shared cache. For an isolated cache, set the same absolute `APISKILL_CACHE_DIR` for every Web, CLI, and MCP process. When developing this repository, you can also start it from the project root:
21
21
 
22
22
  ```bash
23
23
  npm install
package/README.zh.md CHANGED
@@ -17,7 +17,7 @@ npm install -g apiskill
17
17
  apiskill run web
18
18
  ```
19
19
 
20
- 通过 npm 全局安装时,Web 端默认使用用户可写的 `~/.apiskill/cache` 保存版本数据。使用 `--cwd` 可切换为项目目录下的 `<cwd>/cache`,使用 `--cache-dir` 可指定其他位置。开发本仓库时也可以在项目根目录启动:
20
+ Web、CLI MCP 默认统一使用用户可写的 `~/.apiskill/cache`,共同读写同一份文档和版本。`--cwd` 只改变 Web 进程的工作目录,不再改变共享缓存。需要隔离缓存时,必须为 Web、CLI 和 MCP 设置完全相同的绝对路径 `APISKILL_CACHE_DIR`。开发本仓库时也可以在项目根目录启动:
21
21
 
22
22
  ```bash
23
23
  npm install
package/docs/cli.md CHANGED
@@ -14,11 +14,12 @@ apiskill --help
14
14
  apiskill run web
15
15
  ```
16
16
 
17
- `apiskill run web` starts the web console and writes cache data to the user-writable `~/.apiskill/cache` directory by default. `--cwd` uses `<cwd>/cache`; `--cache-dir` selects an explicit location:
17
+ `apiskill run web`, CLI commands, and MCP all use `~/.apiskill/cache` by default. `--cwd` changes only the Web working directory and does not change the shared cache. For project isolation, set the same absolute `APISKILL_CACHE_DIR` for every Web, CLI, and MCP process:
18
18
 
19
19
  ```bash
20
20
  apiskill run web --port 8890
21
- apiskill run web --cwd /path/to/project --cache-dir .apiskill-cache
21
+ APISKILL_CACHE_DIR=/path/to/project/.apiskill-cache apiskill run web --cwd /path/to/project
22
+ APISKILL_CACHE_DIR=/path/to/project/.apiskill-cache apiskill check --json
22
23
  ```
23
24
 
24
25
  From the source project root:
package/docs/cli.zh.md CHANGED
@@ -14,11 +14,12 @@ apiskill --help
14
14
  apiskill run web
15
15
  ```
16
16
 
17
- `apiskill run web` 会启动 Web 控制台,默认把缓存写入用户可写的 `~/.apiskill/cache`。使用 `--cwd` 时缓存位于 `<cwd>/cache`,也可以用 `--cache-dir` 指定其他目录:
17
+ `apiskill run web`、其他 CLI 命令和 MCP 默认统一使用 `~/.apiskill/cache`。`--cwd` 只改变 Web 工作目录,不会改变共享缓存。需要按项目隔离时,必须为 Web、CLI 和 MCP 设置相同的绝对路径 `APISKILL_CACHE_DIR`:
18
18
 
19
19
  ```bash
20
20
  apiskill run web --port 8890
21
- apiskill run web --cwd /path/to/project --cache-dir .apiskill-cache
21
+ APISKILL_CACHE_DIR=/path/to/project/.apiskill-cache apiskill run web --cwd /path/to/project
22
+ APISKILL_CACHE_DIR=/path/to/project/.apiskill-cache apiskill check --json
22
23
  ```
23
24
 
24
25
  在源码项目根目录运行:
package/docs/mcp.ja.md CHANGED
@@ -20,12 +20,10 @@ cwd = "/Users/dobby/dev/apiskill"
20
20
  startup_timeout_sec = 10
21
21
  tool_timeout_sec = 60
22
22
  enabled = true
23
-
24
- [mcp_servers.apiskill.env]
25
- APISKILL_ROOT = "/Users/dobby/dev/apiskill"
26
- APISKILL_CACHE_DIR = "/Users/dobby/dev/apiskill/cache"
27
23
  ```
28
24
 
25
+ キャッシュ環境変数を設定しない場合、MCP、Web、CLI は同じ `~/.apiskill/cache` を使用します。プロジェクトごとに分離する場合は、3 つのプロセスすべてに同じ絶対パスの `APISKILL_CACHE_DIR` を設定してください。
26
+
29
27
  Codex を再起動して `/mcp` を実行します。`apiskill` と以下のツールが表示されるはずです。
30
28
 
31
29
  ## 読み取りツール
package/docs/mcp.ko.md CHANGED
@@ -20,12 +20,10 @@ cwd = "/Users/dobby/dev/apiskill"
20
20
  startup_timeout_sec = 10
21
21
  tool_timeout_sec = 60
22
22
  enabled = true
23
-
24
- [mcp_servers.apiskill.env]
25
- APISKILL_ROOT = "/Users/dobby/dev/apiskill"
26
- APISKILL_CACHE_DIR = "/Users/dobby/dev/apiskill/cache"
27
23
  ```
28
24
 
25
+ 캐시 환경 변수를 설정하지 않으면 MCP, Web, CLI는 모두 같은 `~/.apiskill/cache`를 사용합니다. 프로젝트별로 분리할 때는 세 프로세스에 동일한 절대 경로의 `APISKILL_CACHE_DIR`를 설정하세요.
26
+
29
27
  Codex를 재시작하고 `/mcp`를 실행합니다. `apiskill`과 아래 도구들이 보여야 합니다.
30
28
 
31
29
  ## 읽기 도구
package/docs/mcp.md CHANGED
@@ -22,12 +22,10 @@ cwd = "/Users/dobby/dev/apiskill"
22
22
  startup_timeout_sec = 10
23
23
  tool_timeout_sec = 60
24
24
  enabled = true
25
-
26
- [mcp_servers.apiskill.env]
27
- APISKILL_ROOT = "/Users/dobby/dev/apiskill"
28
- APISKILL_CACHE_DIR = "/Users/dobby/dev/apiskill/cache"
29
25
  ```
30
26
 
27
+ With no cache environment configured, MCP uses the same `~/.apiskill/cache` default as Web and CLI. For project isolation, set one absolute `APISKILL_CACHE_DIR` value and use that exact value for all three processes.
28
+
31
29
  Restart Codex and run `/mcp`. You should see `apiskill` with the tools below.
32
30
 
33
31
  ## Read Tools
package/docs/mcp.zh.md CHANGED
@@ -22,12 +22,10 @@ cwd = "/Users/dobby/dev/apiskill"
22
22
  startup_timeout_sec = 10
23
23
  tool_timeout_sec = 60
24
24
  enabled = true
25
-
26
- [mcp_servers.apiskill.env]
27
- APISKILL_ROOT = "/Users/dobby/dev/apiskill"
28
- APISKILL_CACHE_DIR = "/Users/dobby/dev/apiskill/cache"
29
25
  ```
30
26
 
27
+ 不配置缓存环境变量时,MCP 与 Web、CLI 一样默认使用 `~/.apiskill/cache`。需要按项目隔离时,设置一个绝对路径 `APISKILL_CACHE_DIR`,并确保三个进程使用完全相同的值。
28
+
31
29
  重启 Codex 后运行 `/mcp`,应该能看到 `apiskill` 和下面的工具。
32
30
 
33
31
  ## 只读工具
package/docs/web.md CHANGED
@@ -11,7 +11,7 @@ npm install -g apiskill
11
11
  apiskill run web
12
12
  ```
13
13
 
14
- Global npm installations use the user-writable `~/.apiskill/cache` directory by default. Use `--cwd` for a project-local `<cwd>/cache`, or `--cache-dir` for an explicit path. When developing this repository, you can also use:
14
+ Web, CLI, and MCP use the same user-writable `~/.apiskill/cache` directory by default. `--cwd` changes only the working directory. For an isolated cache, set the same absolute `APISKILL_CACHE_DIR` for all three processes. When developing this repository, you can also use:
15
15
 
16
16
  ```bash
17
17
  npm install
package/docs/web.zh.md CHANGED
@@ -11,7 +11,7 @@ npm install -g apiskill
11
11
  apiskill run web
12
12
  ```
13
13
 
14
- 通过 npm 全局安装时,默认缓存目录是用户可写的 `~/.apiskill/cache`。使用 `--cwd` 可切换为项目目录下的 `<cwd>/cache`,使用 `--cache-dir` 可指定其他位置。开发本仓库时也可以使用:
14
+ Web、CLI MCP 默认统一使用用户可写的 `~/.apiskill/cache`,共享同一份文档和版本。`--cwd` 只改变工作目录。需要隔离缓存时,必须为三个进程设置相同的绝对路径 `APISKILL_CACHE_DIR`。开发本仓库时也可以使用:
15
15
 
16
16
  ```bash
17
17
  npm install
@@ -3,11 +3,7 @@
3
3
  "apiskill": {
4
4
  "command": "node",
5
5
  "args": ["/Users/dobby/dev/apiskill/scripts/mcp-server.mjs"],
6
- "cwd": "/Users/dobby/dev/apiskill",
7
- "env": {
8
- "APISKILL_ROOT": "/Users/dobby/dev/apiskill",
9
- "APISKILL_CACHE_DIR": "/Users/dobby/dev/apiskill/cache"
10
- }
6
+ "cwd": "/Users/dobby/dev/apiskill"
11
7
  }
12
8
  }
13
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiskill",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -22,6 +22,7 @@
22
22
  "dev": "vite --host 0.0.0.0 && node scripts/mcp-server.mjs",
23
23
  "build": "tsc -b && vite build",
24
24
  "test:cli-concurrency": "node scripts/test-cli-concurrency.mjs",
25
+ "test:shared-cache": "node scripts/test-shared-cache.mjs",
25
26
  "prepack": "npm run build",
26
27
  "preview": "vite preview --host 0.0.0.0",
27
28
  "mcp": "node scripts/mcp-server.mjs",
@@ -67,8 +67,7 @@ run
67
67
  .option('-p, --port <number>', 'Web server port', '8888')
68
68
  .option('--host <host>', 'Host to bind', '127.0.0.1')
69
69
  .option('--strict-port', 'Fail if the requested port is already in use')
70
- .option('--cwd <dir>', 'Project directory. Uses <cwd>/cache unless --cache-dir is provided')
71
- .option('--cache-dir <dir>', 'Cache directory. Defaults to ~/.apiskill/cache')
70
+ .option('--cwd <dir>', 'Project working directory. Does not change the shared cache directory')
72
71
  .action(async (options) => {
73
72
  await runWeb(options);
74
73
  });
@@ -297,11 +296,7 @@ function registerMockCommand(command) {
297
296
 
298
297
  async function runWeb(options) {
299
298
  const projectRoot = resolve(options.cwd || process.cwd());
300
- const cacheDir = options.cacheDir
301
- ? resolve(projectRoot, options.cacheDir)
302
- : options.cwd
303
- ? resolve(projectRoot, 'cache')
304
- : getDefaultCacheDir();
299
+ const cacheDir = getDefaultCacheDir();
305
300
  const port = Number(options.port) || 8888;
306
301
  const host = options.host || '127.0.0.1';
307
302
 
@@ -13,6 +13,7 @@ import {
13
13
  saveManualOperation,
14
14
  } from './openapi-store.mjs';
15
15
  import { crawlOpenApi, importFromCurl, importFromLocalFile, importFromUrl } from './openapi-importer.mjs';
16
+ import { getDefaultCacheDir } from './cache-paths.mjs';
16
17
 
17
18
  const MAX_DEPTH = 12;
18
19
 
@@ -37,6 +38,7 @@ export const importExamples = {
37
38
  };
38
39
 
39
40
  export async function checkCache() {
41
+ const cacheDir = getDefaultCacheDir();
40
42
  const versions = await listCachedVersions();
41
43
  try {
42
44
  const { document, meta } = await readCachedDocument();
@@ -46,6 +48,7 @@ export async function checkCache() {
46
48
  return {
47
49
  ok: false,
48
50
  message: 'No usable cached OpenAPI document found. The latest cache exists, but it has no paths.',
51
+ cacheDir,
49
52
  versionsCount: versions.length,
50
53
  latestVersion: metaSummary(meta),
51
54
  importExamples,
@@ -54,6 +57,7 @@ export async function checkCache() {
54
57
  return {
55
58
  ok: true,
56
59
  message: 'API Skill cache is ready.',
60
+ cacheDir,
57
61
  versionsCount: versions.length,
58
62
  latestVersion: {
59
63
  ...metaSummary(meta),
@@ -66,6 +70,7 @@ export async function checkCache() {
66
70
  return {
67
71
  ok: false,
68
72
  message: 'No usable cached OpenAPI document found. Import or crawl a document first.',
73
+ cacheDir,
69
74
  error: error instanceof Error ? error.message : 'Unknown cache read error',
70
75
  versionsCount: versions.length,
71
76
  latestVersion: versions.find((version) => version.latest) ?? versions[0],
@@ -76,6 +81,7 @@ export async function checkCache() {
76
81
 
77
82
  export function formatCheckText(result) {
78
83
  const lines = [];
84
+ if (result.cacheDir) lines.push(`Cache: ${result.cacheDir}`);
79
85
  if (result.ok) {
80
86
  lines.push('OK: API Skill cache is ready.');
81
87
  if (result.latestVersion?.versionId) lines.push(`Latest version: ${result.latestVersion.versionId}`);
@@ -3,6 +3,5 @@ import { resolve } from 'node:path';
3
3
 
4
4
  export function getDefaultCacheDir(env = process.env) {
5
5
  if (env.APISKILL_CACHE_DIR) return resolve(env.APISKILL_CACHE_DIR);
6
- if (env.APISKILL_ROOT) return resolve(env.APISKILL_ROOT, 'cache');
7
6
  return resolve(homedir(), '.apiskill', 'cache');
8
7
  }
@@ -0,0 +1,140 @@
1
+ import assert from 'node:assert/strict';
2
+ import { createServer } from 'node:net';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import { homedir, tmpdir } from 'node:os';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { spawn } from 'node:child_process';
8
+ import { getDefaultCacheDir } from './lib/cache-paths.mjs';
9
+
10
+ const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
11
+ const cliPath = resolve(projectRoot, 'scripts/apiskill-cli.mjs');
12
+ const cacheDir = await mkdtemp(join(tmpdir(), 'apiskill-shared-cache-'));
13
+ const port = await findFreePort();
14
+ let webProcess;
15
+
16
+ try {
17
+ assert.equal(getDefaultCacheDir({ APISKILL_ROOT: '/tmp/another-project' }), resolve(homedir(), '.apiskill', 'cache'));
18
+
19
+ const created = JSON.parse(
20
+ await runCli(['document', 'create', '--title', 'Shared Cache Test', '--doc-version', '1.0.0', '--json']),
21
+ );
22
+ const versionId = created.meta.versionId;
23
+ await runCli([
24
+ 'api',
25
+ 'create',
26
+ '--version',
27
+ versionId,
28
+ '--config',
29
+ JSON.stringify({
30
+ api: {
31
+ method: 'get',
32
+ path: '/api/v1/from-cli',
33
+ summary: 'Created from CLI',
34
+ responses: [{ status: '200', description: 'Success' }],
35
+ },
36
+ }),
37
+ '--json',
38
+ ]);
39
+
40
+ webProcess = spawn(
41
+ process.execPath,
42
+ [cliPath, 'run', 'web', '--host', '127.0.0.1', '--port', String(port), '--strict-port', '--cwd', projectRoot],
43
+ {
44
+ cwd: projectRoot,
45
+ env: { ...process.env, APISKILL_CACHE_DIR: cacheDir },
46
+ stdio: ['ignore', 'pipe', 'pipe'],
47
+ },
48
+ );
49
+ const webOutput = collectProcessOutput(webProcess);
50
+ await waitForWeb(port, webProcess, webOutput);
51
+
52
+ const webCacheResponse = await fetch(`http://127.0.0.1:${port}/api/openapi/cache`);
53
+ assert.equal(webCacheResponse.status, 200);
54
+ const webCache = await webCacheResponse.json();
55
+ assert.equal(webCache.meta.versionId, versionId);
56
+ assert.ok(webCache.document.paths['/api/v1/from-cli']?.get, 'Web did not read the API created by CLI');
57
+
58
+ const webWriteResponse = await fetch(`http://127.0.0.1:${port}/api/openapi/custom-operation`, {
59
+ method: 'POST',
60
+ headers: { 'content-type': 'application/json' },
61
+ body: JSON.stringify({
62
+ versionId,
63
+ config: {
64
+ method: 'post',
65
+ path: '/api/v1/from-web',
66
+ summary: 'Created from Web',
67
+ responses: [{ status: '201', description: 'Created' }],
68
+ },
69
+ }),
70
+ });
71
+ assert.equal(webWriteResponse.status, 200, await webWriteResponse.text());
72
+
73
+ const cliRead = JSON.parse(await runCli(['api', 'query', 'POST', '/api/v1/from-web', '--version', versionId]));
74
+ assert.equal(cliRead.path, '/api/v1/from-web');
75
+ assert.equal(cliRead.method, 'post');
76
+
77
+ const check = JSON.parse(await runCli(['check', '--json']));
78
+ assert.equal(check.cacheDir, cacheDir);
79
+ console.log(`Shared cache test passed: CLI and Web both read and wrote ${cacheDir}.`);
80
+ } finally {
81
+ if (webProcess && webProcess.exitCode === null) {
82
+ webProcess.kill('SIGTERM');
83
+ await new Promise((resolvePromise) => webProcess.once('close', resolvePromise));
84
+ }
85
+ await rm(cacheDir, { recursive: true, force: true });
86
+ }
87
+
88
+ function runCli(args) {
89
+ return new Promise((resolvePromise, rejectPromise) => {
90
+ const child = spawn(process.execPath, [cliPath, ...args], {
91
+ cwd: projectRoot,
92
+ env: { ...process.env, APISKILL_CACHE_DIR: cacheDir },
93
+ stdio: ['ignore', 'pipe', 'pipe'],
94
+ });
95
+ const output = collectProcessOutput(child);
96
+ child.on('error', rejectPromise);
97
+ child.on('close', (code) => {
98
+ if (code === 0) resolvePromise(output.stdout);
99
+ else rejectPromise(new Error(`apiskill ${args.join(' ')} failed (${code}): ${output.stderr || output.stdout}`));
100
+ });
101
+ });
102
+ }
103
+
104
+ function collectProcessOutput(child) {
105
+ const output = { stdout: '', stderr: '' };
106
+ child.stdout.setEncoding('utf8');
107
+ child.stderr.setEncoding('utf8');
108
+ child.stdout.on('data', (chunk) => {
109
+ output.stdout += chunk;
110
+ });
111
+ child.stderr.on('data', (chunk) => {
112
+ output.stderr += chunk;
113
+ });
114
+ return output;
115
+ }
116
+
117
+ async function waitForWeb(webPort, child, output) {
118
+ const deadline = Date.now() + 15_000;
119
+ while (Date.now() < deadline) {
120
+ if (child.exitCode !== null) throw new Error(`Web process exited early (${child.exitCode}): ${output.stderr || output.stdout}`);
121
+ try {
122
+ const response = await fetch(`http://127.0.0.1:${webPort}/api/openapi/versions`);
123
+ if (response.ok) return;
124
+ } catch {}
125
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 100));
126
+ }
127
+ throw new Error(`Timed out waiting for Web: ${output.stderr || output.stdout}`);
128
+ }
129
+
130
+ function findFreePort() {
131
+ return new Promise((resolvePromise, rejectPromise) => {
132
+ const server = createServer();
133
+ server.once('error', rejectPromise);
134
+ server.listen(0, '127.0.0.1', () => {
135
+ const address = server.address();
136
+ const freePort = typeof address === 'object' && address ? address.port : 0;
137
+ server.close((error) => (error ? rejectPromise(error) : resolvePromise(freePort)));
138
+ });
139
+ });
140
+ }