autokap 1.6.2 → 1.6.4

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.
@@ -1,5 +1,18 @@
1
+ // CLI-local config helpers.
2
+ //
3
+ // NOTE: this file currently duplicates the canonical implementation in
4
+ // `@autokap/core/config`. The MCP server consumes the core package directly;
5
+ // the bundled CLI binary still ships a self-contained copy because
6
+ // `@autokap/core` is not yet published to npm. Once core@0.1.0 is on the
7
+ // registry and the published CLI bumps to declare it as a runtime dep, this
8
+ // file can become a thin re-export shim (just LOCAL_WS_URL + requireConfig).
9
+ //
10
+ // Hardening here must stay in lockstep with packages/core/src/config.ts:
11
+ // atomic writes, scheme allowlist on URL inputs, env-var precedence, and
12
+ // the "untrusted origin override" guard.
1
13
  import path from 'node:path';
2
14
  import os from 'node:os';
15
+ import crypto from 'node:crypto';
3
16
  import fs from 'node:fs/promises';
4
17
  import { logger } from './logger.js';
5
18
  const DEFAULT_API_BASE_URL = 'https://autokap.app';
@@ -46,45 +59,76 @@ export async function readConfig() {
46
59
  wsUrl,
47
60
  };
48
61
  }
62
+ // Only swallow IO / parse failures. Origin-allowlist / schema errors must
63
+ // bubble up so the user sees what's wrong instead of "not authenticated".
64
+ let raw;
49
65
  try {
50
- const raw = await fs.readFile(getConfigPath(), 'utf-8');
51
- const parsed = JSON.parse(raw);
52
- if (!parsed.apiKey)
53
- return null;
54
- const envApiBaseUrl = normalizeUrl(process.env[API_BASE_URL_ENV_VAR]);
55
- const envWsUrl = normalizeUrl(process.env[WS_URL_ENV_VAR]);
56
- const storedApiBaseUrl = normalizeUrl(parsed.apiBaseUrl) ?? DEFAULT_API_BASE_URL;
57
- const apiBaseUrl = envApiBaseUrl ?? storedApiBaseUrl;
58
- const wsUrl = envWsUrl
59
- ?? (envApiBaseUrl ? deriveWsUrl(apiBaseUrl) : normalizeUrl(parsed.wsUrl) ?? deriveWsUrl(apiBaseUrl));
60
- assertAllowedApiOrigin(apiBaseUrl, storedApiBaseUrl, envApiBaseUrl ? API_BASE_URL_ENV_VAR : undefined);
61
- if (envWsUrl) {
62
- assertAllowedApiOrigin(wsUrl.replace(/^ws/, 'http'), deriveWsUrl(storedApiBaseUrl).replace(/^ws/, 'http'), WS_URL_ENV_VAR);
63
- }
64
- return {
65
- apiKey: parsed.apiKey,
66
- apiBaseUrl,
67
- wsUrl,
68
- };
66
+ raw = await fs.readFile(getConfigPath(), 'utf-8');
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ let parsed;
72
+ try {
73
+ parsed = JSON.parse(raw);
69
74
  }
70
75
  catch {
71
76
  return null;
72
77
  }
78
+ if (!parsed || typeof parsed !== 'object')
79
+ return null;
80
+ const record = parsed;
81
+ if (typeof record.apiKey !== 'string' || record.apiKey.length === 0)
82
+ return null;
83
+ const storedApiBaseUrlRaw = typeof record.apiBaseUrl === 'string' ? record.apiBaseUrl : null;
84
+ const storedWsUrlRaw = typeof record.wsUrl === 'string' ? record.wsUrl : null;
85
+ const envApiBaseUrl = normalizeUrl(process.env[API_BASE_URL_ENV_VAR]);
86
+ const envWsUrl = normalizeUrl(process.env[WS_URL_ENV_VAR]);
87
+ const storedApiBaseUrl = normalizeUrl(storedApiBaseUrlRaw) ?? DEFAULT_API_BASE_URL;
88
+ const apiBaseUrl = envApiBaseUrl ?? storedApiBaseUrl;
89
+ const wsUrl = envWsUrl ??
90
+ (envApiBaseUrl
91
+ ? deriveWsUrl(apiBaseUrl)
92
+ : normalizeUrl(storedWsUrlRaw) ?? deriveWsUrl(apiBaseUrl));
93
+ assertAllowedApiOrigin(apiBaseUrl, storedApiBaseUrl, envApiBaseUrl ? API_BASE_URL_ENV_VAR : undefined);
94
+ if (envWsUrl) {
95
+ assertAllowedApiOrigin(wsUrl.replace(/^ws/, 'http'), deriveWsUrl(storedApiBaseUrl).replace(/^ws/, 'http'), WS_URL_ENV_VAR);
96
+ }
97
+ return {
98
+ apiKey: record.apiKey,
99
+ apiBaseUrl,
100
+ wsUrl,
101
+ };
73
102
  }
74
103
  export async function writeConfig(config) {
75
104
  const dir = getConfigDir();
76
105
  await fs.mkdir(dir, { recursive: true });
106
+ if (process.platform !== 'win32') {
107
+ try {
108
+ await fs.chmod(dir, 0o700);
109
+ }
110
+ catch {
111
+ // best-effort — the file's 0600 below is the real safety
112
+ }
113
+ }
77
114
  const configPath = getConfigPath();
78
115
  const normalizedConfig = {
79
116
  ...config,
80
117
  apiBaseUrl: normalizeUrl(config.apiBaseUrl) ?? DEFAULT_API_BASE_URL,
81
118
  wsUrl: normalizeUrl(config.wsUrl) ?? deriveWsUrl(config.apiBaseUrl),
82
119
  };
83
- await fs.writeFile(configPath, JSON.stringify(normalizedConfig, null, 2), 'utf-8');
84
- // Restrict file permissions on Unix (NTFS ignores POSIX modes)
85
- if (process.platform !== 'win32') {
86
- await fs.chmod(dir, 0o700);
87
- await fs.chmod(configPath, 0o600);
120
+ // Atomic write — see packages/core/src/config.ts for the canonical impl.
121
+ const tmpPath = `${configPath}.${process.pid}.${crypto.randomBytes(4).toString('hex')}.tmp`;
122
+ try {
123
+ await fs.writeFile(tmpPath, JSON.stringify(normalizedConfig, null, 2), 'utf-8');
124
+ if (process.platform !== 'win32') {
125
+ await fs.chmod(tmpPath, 0o600);
126
+ }
127
+ await fs.rename(tmpPath, configPath);
128
+ }
129
+ catch (err) {
130
+ await fs.rm(tmpPath, { force: true }).catch(() => undefined);
131
+ throw err;
88
132
  }
89
133
  }
90
134
  export async function deleteConfig() {
@@ -105,7 +149,7 @@ export async function requireConfig() {
105
149
  process.exit(1);
106
150
  }
107
151
  if (!config) {
108
- logger.error(`Not authenticated. Run \`npx autokap@latest init --cli-key <key>\` first, or set ${API_KEY_ENV_VAR} in CI.`);
152
+ logger.error(`Not authenticated. Authenticate via your IDE's MCP integration (autokap_authenticate) or run \`autokap login <key>\` for CI / Cloud Run. CI may also set ${API_KEY_ENV_VAR} directly.`);
109
153
  process.exit(1);
110
154
  }
111
155
  return config;
@@ -4,15 +4,29 @@ export interface CliPublicCommandDescriptor {
4
4
  summary: string;
5
5
  docsDescriptionKey?: string;
6
6
  }
7
- export declare const CLI_KEY_TERM = "CLI key";
7
+ export declare const API_KEY_TERM = "API key";
8
+ /**
9
+ * Back-compat alias for internal callers that still import `CLI_KEY_TERM`.
10
+ * Points at the same string as `API_KEY_TERM`.
11
+ */
12
+ export declare const CLI_KEY_TERM = "API key";
13
+ /** HTTP header sent by the bundled CLI binary on every request. */
8
14
  export declare const CLI_VERSION_HEADER = "x-autokap-cli-version";
9
15
  export declare const MIN_CLI_VERSION_FOR_SIGNED_PROGRAMS = "1.0.9";
10
16
  export declare const MIN_CLI_VERSION_FOR_DIRECT_ARTIFACT_UPLOADS = "1.5.0";
11
- export declare const CLI_DEFAULT_INSTALL_COMMAND = "npm install -g autokap";
12
- export declare const CLI_DEFAULT_INSTALLED_SETUP_COMMAND = "autokap init --cli-key <your-cli-key>";
13
- export declare const CLI_DEFAULT_SETUP_COMMAND = "npx autokap@latest init --cli-key <your-cli-key>";
14
- export declare const CLI_ADVANCED_SKILL_COMMAND = "autokap skill --agent <agent>";
17
+ export declare const CLI_DEFAULT_INSTALL_COMMAND = "npx -y autokap@latest";
18
+ export declare const CLI_DEFAULT_INSTALLED_SETUP_COMMAND = "autokap login <your-api-key>";
19
+ export declare const CLI_DEFAULT_SETUP_COMMAND = "npx -y autokap@latest login <your-api-key>";
15
20
  export declare const CLI_FALLBACK_PROGRAM_COMMAND = "autokap run <preset-id> --program <file>";
21
+ /** Bare MCP runner. Used by generic clients and as the npx command. */
22
+ export declare const MCP_INSTALL_COMMAND = "npx -y @autokap/mcp";
23
+ /** One-shot install command for Claude Code (CLI flag `claude mcp add`). */
24
+ export declare const MCP_CLAUDE_CODE_COMMAND = "claude mcp add autokap -- npx -y @autokap/mcp";
25
+ /**
26
+ * JSON snippet to merge into the IDE-specific MCP config file (Cursor, Codex,
27
+ * Windsurf, Cline...). Format follows the shared `mcpServers` schema.
28
+ */
29
+ export declare const MCP_CONFIG_SNIPPET_JSON: string;
16
30
  export declare function buildCliRunCommand(presetId: string, options?: {
17
31
  local?: boolean;
18
32
  env?: string;
@@ -1,12 +1,54 @@
1
- export const CLI_KEY_TERM = "CLI key";
1
+ export const API_KEY_TERM = "API key";
2
+ /**
3
+ * Back-compat alias for internal callers that still import `CLI_KEY_TERM`.
4
+ * Points at the same string as `API_KEY_TERM`.
5
+ */
6
+ export const CLI_KEY_TERM = API_KEY_TERM;
7
+ /** HTTP header sent by the bundled CLI binary on every request. */
2
8
  export const CLI_VERSION_HEADER = "x-autokap-cli-version";
3
9
  export const MIN_CLI_VERSION_FOR_SIGNED_PROGRAMS = "1.0.9";
4
10
  export const MIN_CLI_VERSION_FOR_DIRECT_ARTIFACT_UPLOADS = "1.5.0";
5
- export const CLI_DEFAULT_INSTALL_COMMAND = "npm install -g autokap";
6
- export const CLI_DEFAULT_INSTALLED_SETUP_COMMAND = "autokap init --cli-key <your-cli-key>";
7
- export const CLI_DEFAULT_SETUP_COMMAND = "npx autokap@latest init --cli-key <your-cli-key>";
8
- export const CLI_ADVANCED_SKILL_COMMAND = "autokap skill --agent <agent>";
11
+ // MCP-first install. The bundled CLI binary ships only for CI / Cloud Run;
12
+ // recommend `npx -y autokap@latest` over a global install to keep version
13
+ // drift contained and avoid the npm supply-chain anxiety the migration was
14
+ // designed to remove. The `CLI_DEFAULT_INSTALL_COMMAND` alias is retained for
15
+ // legacy doc surfaces that still want a single shell snippet.
16
+ export const CLI_DEFAULT_INSTALL_COMMAND = "npx -y autokap@latest";
17
+ // `autokap init` was removed in v2. The setup command now uses the surviving
18
+ // `autokap login` for the same purpose (store the API key in ~/.autokap/config.json).
19
+ // MCP-first workflows should use `autokap_authenticate` from the IDE instead.
20
+ export const CLI_DEFAULT_INSTALLED_SETUP_COMMAND = "autokap login <your-api-key>";
21
+ export const CLI_DEFAULT_SETUP_COMMAND = "npx -y autokap@latest login <your-api-key>";
22
+ // `autokap skill --agent` was removed in v2. The skill ships as the MCP resource
23
+ // `autokap://skill/preset-spec` (or `autokap_get_skill` tool fallback). The
24
+ // legacy `CLI_ADVANCED_SKILL_COMMAND` constant that pointed at the removed
25
+ // command was dropped entirely — it had no consumers and any future doc page
26
+ // surfacing it would mislead users into typing a non-existent command.
9
27
  export const CLI_FALLBACK_PROGRAM_COMMAND = "autokap run <preset-id> --program <file>";
28
+ // ── MCP-first install contract ──────────────────────────────────────
29
+ //
30
+ // AutoKap is primarily exposed as a Model Context Protocol server via the
31
+ // `@autokap/mcp` package. Every supported IDE installs the server by adding a
32
+ // single stdio entry that runs `npx -y @autokap/mcp` — the constants below
33
+ // are the canonical shell forms used across onboarding surfaces (wizard,
34
+ // modal, banner, docs). For per-IDE payloads (config paths, snippets, notes),
35
+ // use `getMcpInstallEntries()` from `web/lib/site-discovery.ts`.
36
+ /** Bare MCP runner. Used by generic clients and as the npx command. */
37
+ export const MCP_INSTALL_COMMAND = "npx -y @autokap/mcp";
38
+ /** One-shot install command for Claude Code (CLI flag `claude mcp add`). */
39
+ export const MCP_CLAUDE_CODE_COMMAND = "claude mcp add autokap -- npx -y @autokap/mcp";
40
+ /**
41
+ * JSON snippet to merge into the IDE-specific MCP config file (Cursor, Codex,
42
+ * Windsurf, Cline...). Format follows the shared `mcpServers` schema.
43
+ */
44
+ export const MCP_CONFIG_SNIPPET_JSON = JSON.stringify({
45
+ mcpServers: {
46
+ autokap: {
47
+ command: "npx",
48
+ args: ["-y", "@autokap/mcp"],
49
+ },
50
+ },
51
+ }, null, 2);
10
52
  export function buildCliRunCommand(presetId, options = {}) {
11
53
  const flags = [
12
54
  options.local ? " --local" : "",
@@ -17,170 +59,32 @@ export function buildCliRunCommand(presetId, options = {}) {
17
59
  return `autokap run${flags} ${presetId}`;
18
60
  }
19
61
  export function buildCliInstalledSetupCommand(cliKey) {
20
- return `autokap init --cli-key ${cliKey}`;
62
+ return `autokap login ${cliKey}`;
21
63
  }
22
64
  export const CLI_PUBLIC_COMMANDS = [
23
- {
24
- id: "init",
25
- command: "autokap init",
26
- summary: "Authenticate and install the AI skill in one step",
27
- docsDescriptionKey: "cliCmdInit",
28
- },
29
65
  {
30
66
  id: "login",
31
67
  command: "autokap login <key>",
32
- summary: "Authenticate with your CLI key",
68
+ summary: "Authenticate with your API key (used by Cloud Run + CI; from the IDE, call autokap_authenticate via MCP instead)",
33
69
  docsDescriptionKey: "cliCmdLogin",
34
70
  },
35
- {
36
- id: "logout",
37
- command: "autokap logout",
38
- summary: "Remove stored credentials",
39
- docsDescriptionKey: "cliCmdLogout",
40
- },
41
- {
42
- id: "whoami",
43
- command: "autokap whoami",
44
- summary: "Show the account linked to the current CLI key",
45
- docsDescriptionKey: "cliCmdWhoami",
46
- },
47
- {
48
- id: "ping",
49
- command: "autokap ping",
50
- summary: "Verify CLI connection and confirm the stored key is still valid",
51
- docsDescriptionKey: "cliCmdPing",
52
- },
53
71
  {
54
72
  id: "run",
55
73
  command: "autokap run <preset-id> --env local",
56
- summary: "Run a preset capture using local Playwright",
74
+ summary: "Run a preset capture using local Playwright (also spawned by autokap_start_capture from the MCP server)",
57
75
  docsDescriptionKey: "cliCmdRun",
58
76
  },
59
77
  {
60
78
  id: "auto-recapture",
61
79
  command: "autokap auto-recapture --project <project-id> --env local",
62
- summary: "Run every preset enabled for Recapture Cloud in a project",
80
+ summary: "Run every preset enabled for Recapture Cloud in a project (the canonical surface for Cloud Run + CI; MCP exposes autokap_start_auto_recapture for end users)",
63
81
  docsDescriptionKey: "cliCmdAutoRecapture",
64
82
  },
65
83
  {
66
- id: "run-headed",
67
- command: "autokap run <preset-id> --headed",
68
- summary: "Show the browser window for debugging",
69
- docsDescriptionKey: "cliCmdHeaded",
70
- },
71
- {
72
- id: "run-dry",
73
- command: "autokap run <preset-id> --dry",
74
- summary: "Dry run: execute the opcode program without capturing or uploading (0 credits charged)",
75
- docsDescriptionKey: "cliCmdRunDry",
76
- },
77
- {
78
- id: "project-list",
79
- command: "autokap project list",
80
- summary: "List accessible projects as JSON",
81
- docsDescriptionKey: "cliCmdProjectList",
82
- },
83
- {
84
- id: "project-get",
85
- command: "autokap project get <project-id>",
86
- summary: "Get a single project as JSON",
87
- docsDescriptionKey: "cliCmdProjectGet",
88
- },
89
- {
90
- id: "project-create",
91
- command: "autokap project create --name <name> --url <url>",
92
- summary: "Create a project and print its ID",
93
- docsDescriptionKey: "cliCmdProjectCreate",
94
- },
95
- {
96
- id: "preset-info",
97
- command: "autokap preset info <preset-id>",
98
- summary: "Get structured JSON with endpoints, variants, and URL parameters",
99
- docsDescriptionKey: "cliCmdPresetInfo",
100
- },
101
- {
102
- id: "capture-list",
103
- command: "autokap capture list",
104
- summary: "List captures as JSON",
105
- docsDescriptionKey: "cliCmdCaptureList",
106
- },
107
- {
108
- id: "usage",
109
- command: "autokap usage",
110
- summary: "Show the current billing-period usage as JSON",
111
- docsDescriptionKey: "cliCmdUsage",
112
- },
113
- {
114
- id: "endpoints-list",
115
- command: "autokap endpoints list --preset <id>",
116
- summary: "List dev link endpoints as JSON or table",
117
- docsDescriptionKey: "cliCmdEndpointsList",
118
- },
119
- {
120
- id: "endpoints-get",
121
- command: "autokap endpoints get <endpoint-id>",
122
- summary: "Get a single dev link endpoint as JSON",
123
- docsDescriptionKey: "cliCmdEndpointsGet",
124
- },
125
- {
126
- id: "endpoints-export",
127
- command: "autokap endpoints export --preset <id>",
128
- summary: "Export dev link endpoints as JSON or CSV",
129
- docsDescriptionKey: "cliCmdEndpointsExport",
130
- },
131
- {
132
- id: "auth-capture",
133
- command: "autokap auth capture <project-id>",
134
- summary: "Capture a browser session for a session account",
135
- docsDescriptionKey: "cliCmdAuthCapture",
136
- },
137
- {
138
- id: "auth-account-list",
139
- command: "autokap auth account list <project-id>",
140
- summary: "List project credential accounts as JSON",
141
- docsDescriptionKey: "cliCmdAuthAccountList",
142
- },
143
- {
144
- id: "auth-account-create",
145
- command: "autokap auth account create <project-id> --name <name>",
146
- summary: "Create a project credential account and print its ID",
147
- docsDescriptionKey: "cliCmdAuthAccountCreate",
148
- },
149
- {
150
- id: "auth-account-update",
151
- command: "autokap auth account update <project-id> --account <name-or-id>",
152
- summary: "Update a project credential account",
153
- docsDescriptionKey: "cliCmdAuthAccountUpdate",
154
- },
155
- {
156
- id: "auth-account-delete",
157
- command: "autokap auth account delete <project-id> --account <name-or-id>",
158
- summary: "Delete an unused project credential account",
159
- docsDescriptionKey: "cliCmdAuthAccountDelete",
160
- },
161
- {
162
- id: "auth-session-clear",
163
- command: "autokap auth session clear <project-id> --account <name-or-id>",
164
- summary: "Clear the stored browser session for a session account",
165
- docsDescriptionKey: "cliCmdAuthSessionClear",
166
- },
167
- {
168
- id: "proxy-register",
169
- command: "autokap proxy register --project <id> --proxy-url <url>",
170
- summary: "Register a user-side proxy for dev links",
171
- docsDescriptionKey: "cliCmdProxyRegister",
172
- },
173
- {
174
- id: "proxy-verify",
175
- command: "autokap proxy verify --project <id>",
176
- summary: "Verify the configured proxy for a project",
177
- docsDescriptionKey: "cliCmdProxyVerify",
178
- },
179
- {
180
- id: "skill",
181
- command: "autokap skill",
182
- summary: "Export the AI agent skill bundle",
183
- docsDescriptionKey: "cliCmdSkill",
84
+ id: "doctor",
85
+ command: "autokap doctor",
86
+ summary: "Run environment diagnostics (Node version, config, API key validity, Chromium cache, ffmpeg)",
87
+ docsDescriptionKey: "cliCmdDoctor",
184
88
  },
185
89
  {
186
90
  id: "version",
@@ -1,5 +1,4 @@
1
1
  export declare function runDoctor(opts: {
2
2
  fix: boolean;
3
- agent?: string;
4
3
  json?: boolean;
5
4
  }, currentVersion: string): Promise<void>;
@@ -1,21 +1,11 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import fs from 'node:fs/promises';
3
- import path from 'node:path';
4
3
  import chalk from 'chalk';
5
4
  import { chromium } from 'playwright';
6
5
  import { logger } from './logger.js';
7
6
  import { ensureFfmpegAvailable } from './clip-postprocess.js';
8
7
  import { readConfig, getConfigPath } from './cli-config.js';
9
- import { getCachedOrFetchLatest, isNewerVersion } from './version-check.js';
10
- import { writeSkillExport } from './skill-packaging.js';
11
8
  const REQUIRED_NODE_MAJOR = 20;
12
- const AGENT_PATHS = {
13
- claude: '.claude/commands/autokap-preset.md',
14
- codex: '.agents/skills/autokap-preset/SKILL.md',
15
- cursor: '.cursor/rules/autokap-preset.md',
16
- windsurf: '.windsurf/rules/autokap-preset.md',
17
- copilot: '.github/instructions/autokap-preset.instructions.md',
18
- };
19
9
  async function pathExists(candidate) {
20
10
  try {
21
11
  await fs.access(candidate);
@@ -108,7 +98,7 @@ async function checkConfig() {
108
98
  name: 'CLI configuration',
109
99
  status: 'fail',
110
100
  message: `Failed to read config: ${err.message}`,
111
- fixCommand: 'autokap init --cli-key <key>',
101
+ fixCommand: 'autokap login <key> # or call autokap_authenticate via your MCP-enabled IDE',
112
102
  };
113
103
  }
114
104
  if (!config) {
@@ -116,7 +106,7 @@ async function checkConfig() {
116
106
  name: 'CLI configuration',
117
107
  status: 'fail',
118
108
  message: `No API key found. Checked AUTOKAP_RUN_TOKEN, AUTOKAP_API_KEY, and ${getConfigPath()}.`,
119
- fixCommand: 'autokap init --cli-key <key>',
109
+ fixCommand: 'autokap login <key> # or call autokap_authenticate via your MCP-enabled IDE',
120
110
  };
121
111
  }
122
112
  if (!config.apiKey) {
@@ -124,7 +114,7 @@ async function checkConfig() {
124
114
  name: 'CLI configuration',
125
115
  status: 'fail',
126
116
  message: 'Config exists but apiKey is empty.',
127
- fixCommand: 'autokap init --cli-key <key>',
117
+ fixCommand: 'autokap login <key>',
128
118
  };
129
119
  }
130
120
  return {
@@ -133,78 +123,6 @@ async function checkConfig() {
133
123
  message: `apiBaseUrl=${config.apiBaseUrl}`,
134
124
  };
135
125
  }
136
- async function detectAgent() {
137
- const detectors = [
138
- ['.claude', 'claude'],
139
- ['.agents', 'codex'],
140
- ['.cursor', 'cursor'],
141
- ['.windsurf', 'windsurf'],
142
- ['.github/instructions', 'copilot'],
143
- ];
144
- for (const [dir, key] of detectors) {
145
- if (await pathExists(path.join(process.cwd(), dir))) {
146
- return key;
147
- }
148
- }
149
- return null;
150
- }
151
- async function parseSkillVersion(skillPath) {
152
- try {
153
- const raw = await fs.readFile(skillPath, 'utf8');
154
- const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
155
- if (!match)
156
- return null;
157
- const yamlText = match[1];
158
- for (const line of yamlText.split(/\r?\n/)) {
159
- const m = line.match(/^\s*version\s*:\s*(.+?)\s*$/);
160
- if (m) {
161
- return m[1].replace(/^["']|["']$/g, '').trim() || null;
162
- }
163
- }
164
- return null;
165
- }
166
- catch {
167
- return null;
168
- }
169
- }
170
- async function checkSkill(extras, agentOverride) {
171
- const agent = agentOverride ?? (await detectAgent());
172
- extras.agentDetected = agent;
173
- if (!agent) {
174
- return {
175
- name: 'AI agent skill',
176
- status: 'warn',
177
- message: 'No agent directory detected (.claude, .agents, .cursor, .windsurf, .github/instructions). Skipping skill check.',
178
- fixCommand: 'autokap init # or autokap skill --agent <name> to install',
179
- };
180
- }
181
- const skillPath = path.join(process.cwd(), AGENT_PATHS[agent]);
182
- if (await pathExists(skillPath)) {
183
- extras.skillPath = AGENT_PATHS[agent];
184
- extras.skillVersion = await parseSkillVersion(skillPath);
185
- const versionSuffix = extras.skillVersion ? ` (v${extras.skillVersion})` : '';
186
- return {
187
- name: 'AI agent skill',
188
- status: 'ok',
189
- message: `${agent}: ${AGENT_PATHS[agent]}${versionSuffix}`,
190
- };
191
- }
192
- extras.skillPath = AGENT_PATHS[agent];
193
- return {
194
- name: 'AI agent skill',
195
- status: 'warn',
196
- message: `${agent} agent detected but skill is missing at ${AGENT_PATHS[agent]}.`,
197
- fixCommand: `autokap skill --agent ${agent}`,
198
- fixFn: async () => {
199
- await writeSkillExport({
200
- type: 'preset',
201
- agent,
202
- outputPath: AGENT_PATHS[agent],
203
- placeholders: {},
204
- });
205
- },
206
- };
207
- }
208
126
  async function checkCliKeyValid() {
209
127
  let config = null;
210
128
  try {
@@ -215,68 +133,46 @@ async function checkCliKeyValid() {
215
133
  }
216
134
  if (!config || !config.apiKey) {
217
135
  return {
218
- name: 'CLI key valid',
136
+ name: 'API key valid',
219
137
  status: 'warn',
220
- message: 'No CLI key configured locally — skipping remote validation.',
221
- fixCommand: 'autokap init --cli-key <key>',
138
+ message: 'No API key configured locally — skipping remote validation.',
139
+ fixCommand: 'autokap login <key> # or call autokap_authenticate via your MCP-enabled IDE',
222
140
  };
223
141
  }
224
142
  try {
225
143
  const res = await fetch(`${config.apiBaseUrl}/api/cli/validate`, {
226
144
  headers: { Authorization: `Bearer ${config.apiKey}` },
145
+ signal: AbortSignal.timeout(15_000),
227
146
  });
228
147
  if (res.ok) {
229
148
  return {
230
- name: 'CLI key valid',
149
+ name: 'API key valid',
231
150
  status: 'ok',
232
151
  message: `Key validated against ${config.apiBaseUrl}.`,
233
152
  };
234
153
  }
235
154
  if (res.status === 401 || res.status === 403) {
236
155
  return {
237
- name: 'CLI key valid',
156
+ name: 'API key valid',
238
157
  status: 'fail',
239
- message: `Server rejected the CLI key (HTTP ${res.status}). It was likely revoked or rotated.`,
240
- fixCommand: 'autokap init --cli-key <new-key>',
158
+ message: `Server rejected the API key (HTTP ${res.status}). It was likely revoked or rotated.`,
159
+ fixCommand: 'autokap login <new-key>',
241
160
  };
242
161
  }
243
162
  return {
244
- name: 'CLI key valid',
163
+ name: 'API key valid',
245
164
  status: 'warn',
246
165
  message: `Validation endpoint returned HTTP ${res.status}.`,
247
166
  };
248
167
  }
249
168
  catch (err) {
250
169
  return {
251
- name: 'CLI key valid',
170
+ name: 'API key valid',
252
171
  status: 'warn',
253
172
  message: `Cannot reach validation endpoint: ${err.message}`,
254
173
  };
255
174
  }
256
175
  }
257
- async function checkCliVersion(currentVersion) {
258
- const latest = await getCachedOrFetchLatest();
259
- if (!latest) {
260
- return {
261
- name: 'CLI version',
262
- status: 'warn',
263
- message: `${currentVersion} (latest version check unavailable — offline?)`,
264
- };
265
- }
266
- if (isNewerVersion(latest, currentVersion)) {
267
- return {
268
- name: 'CLI version',
269
- status: 'warn',
270
- message: `${currentVersion} installed, ${latest} available.`,
271
- fixCommand: 'npm install -g autokap@latest',
272
- };
273
- }
274
- return {
275
- name: 'CLI version',
276
- status: 'ok',
277
- message: `${currentVersion} (latest)`,
278
- };
279
- }
280
176
  function runCommandStreaming(command, args) {
281
177
  return new Promise((resolve, reject) => {
282
178
  const child = spawn(command, args, {
@@ -320,15 +216,12 @@ function printCheck(result) {
320
216
  }
321
217
  console.log('');
322
218
  }
323
- function buildJsonReport(results, extras, currentVersion) {
219
+ function buildJsonReport(results, currentVersion) {
324
220
  const ok = results.filter(r => r.status === 'ok').length;
325
221
  const warn = results.filter(r => r.status === 'warn').length;
326
222
  const fail = results.filter(r => r.status === 'fail').length;
327
223
  return {
328
224
  cli_version: currentVersion,
329
- agent_detected: extras.agentDetected,
330
- skill_path: extras.skillPath,
331
- skill_version: extras.skillVersion,
332
225
  checks: results.map(r => ({
333
226
  name: r.name,
334
227
  status: r.status,
@@ -340,42 +233,21 @@ function buildJsonReport(results, extras, currentVersion) {
340
233
  };
341
234
  }
342
235
  export async function runDoctor(opts, currentVersion) {
343
- const agentOverride = opts.agent
344
- ? opts.agent.toLowerCase()
345
- : undefined;
346
236
  const jsonMode = Boolean(opts.json);
347
- if (agentOverride && !(agentOverride in AGENT_PATHS)) {
348
- if (jsonMode) {
349
- process.stdout.write(JSON.stringify({
350
- error: `Unknown agent "${opts.agent}". Supported: ${Object.keys(AGENT_PATHS).join(', ')}`,
351
- }) + '\n');
352
- }
353
- else {
354
- logger.error(`Unknown agent "${opts.agent}". Supported: ${Object.keys(AGENT_PATHS).join(', ')}`);
355
- }
356
- process.exit(1);
357
- }
358
237
  if (!jsonMode) {
359
238
  console.log(chalk.bold('\nautokap doctor'));
360
239
  console.log(chalk.gray(`Checking environment for autokap ${currentVersion}...\n`));
361
240
  }
362
- const extras = {
363
- agentDetected: null,
364
- skillPath: null,
365
- skillVersion: null,
366
- };
367
241
  const results = [];
368
242
  results.push(checkNodeVersion());
369
243
  results.push(await checkChromium());
370
244
  results.push(await checkFfmpeg());
371
245
  results.push(await checkConfig());
372
246
  results.push(await checkCliKeyValid());
373
- results.push(await checkSkill(extras, agentOverride));
374
- results.push(await checkCliVersion(currentVersion));
375
247
  const failures = results.filter(r => r.status === 'fail');
376
248
  const warnings = results.filter(r => r.status === 'warn');
377
249
  if (jsonMode) {
378
- const report = buildJsonReport(results, extras, currentVersion);
250
+ const report = buildJsonReport(results, currentVersion);
379
251
  process.stdout.write(JSON.stringify(report, null, 2) + '\n');
380
252
  process.exit(failures.length > 0 ? 1 : 0);
381
253
  }