@clien-ai/mcp 0.1.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/dist/cli.js ADDED
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `clien-mcp` CLI entry.
4
+ *
5
+ * Subcommands (argv[2]):
6
+ * - (omitted) Start the MCP stdio server (how Claude Code invokes us).
7
+ * - install Register the server in Claude Code's config.
8
+ * - logout Clear stored credentials so the next run re-OAuths.
9
+ * - status Print which storage backend holds the token + token age.
10
+ * - help Print usage.
11
+ * - version Print the version.
12
+ *
13
+ * Global flags:
14
+ * --debug Surface stack traces in fatal errors.
15
+ *
16
+ * Exit codes (see ../util/exit-codes.ts for the canonical map):
17
+ * 0 success, 1 generic, 2 auth-fail, 3 fs-fail, 4 network-fail, 5 invalid-args
18
+ */
19
+ import { runAuthorize } from './commands/authorize.js';
20
+ import { runInstall } from './commands/install.js';
21
+ import { runLogout } from './commands/logout.js';
22
+ import { runStatus } from './commands/status.js';
23
+ import { startStdioServer } from './server.js';
24
+ import { PACKAGE_NAME, PACKAGE_VERSION } from './config.js';
25
+ import { EXIT_CODES, classifyError } from './util/exit-codes.js';
26
+ // ---------- Help / usage ----------
27
+ function printUsage(stream = process.stdout) {
28
+ stream.write([
29
+ `${PACKAGE_NAME} v${PACKAGE_VERSION}`,
30
+ '',
31
+ 'Usage:',
32
+ ' npx @clien-ai/mcp Start the MCP server (how Claude Code invokes us).',
33
+ ' npx @clien-ai/mcp install [--json] Register the server in Claude Code config.',
34
+ ' [--no-browser]',
35
+ ' npx @clien-ai/mcp authorize --token <r> Seed a refresh token (headless / non-TTY auth).',
36
+ ' npx @clien-ai/mcp logout [--json] Clear stored credentials.',
37
+ ' npx @clien-ai/mcp status [--json] Show backend, token presence, last refresh.',
38
+ ' npx @clien-ai/mcp help Show this help.',
39
+ ' npx @clien-ai/mcp version Show the version.',
40
+ '',
41
+ 'Global flags:',
42
+ ' --debug Show stack traces on fatal errors.',
43
+ '',
44
+ 'Env:',
45
+ ' CLIEN_MCP_ENV=test|prod Switch between test and prod environments.',
46
+ ' CLIEN_MCP_SERVER_URL=... Override the Clien.ai server URL.',
47
+ ' CLIEN_MCP_SUPABASE_URL=... Override the Supabase auth URL.',
48
+ ' CLIEN_MCP_CLIENT_ID=... Override the OAuth client id.',
49
+ ' CLIEN_MCP_CLAUDE_CONFIG=... Path to Claude Code config (install only).',
50
+ ' CLIEN_MCP_REFRESH_TOKEN=... Pre-seeded refresh token (skips browser OAuth).',
51
+ '',
52
+ 'Exit codes:',
53
+ ' 0 success',
54
+ ' 1 generic / uncategorized error',
55
+ ' 2 authentication failed',
56
+ ' 3 filesystem operation failed',
57
+ ' 4 network operation failed',
58
+ ' 5 invalid arguments / unknown flag',
59
+ '',
60
+ 'Docs: https://clien.ai',
61
+ '',
62
+ ].join('\n'));
63
+ }
64
+ // ---------- Flag validation with Levenshtein suggestion ----------
65
+ const GLOBAL_FLAGS = ['--debug', '-h', '--help', '-v', '--version'];
66
+ const ALLOWED_FLAGS_BY_COMMAND = {
67
+ install: ['--no-browser', '--json'],
68
+ // `authorize` accepts the refresh token via `--token VALUE`, `--token=VALUE`,
69
+ // or `-t VALUE` (added in cluster 1's headless-OAuth fix). Without this
70
+ // entry the flag validation rejects valid invocations as "Unknown flag".
71
+ authorize: ['--token', '-t'],
72
+ logout: ['--json'],
73
+ status: ['--json'],
74
+ help: [],
75
+ version: [],
76
+ '': [], // default (server mode)
77
+ serve: [],
78
+ server: [],
79
+ start: [],
80
+ };
81
+ /** Crude Levenshtein for "did you mean" suggestions. */
82
+ function editDistance(a, b) {
83
+ if (a === b)
84
+ return 0;
85
+ if (!a.length)
86
+ return b.length;
87
+ if (!b.length)
88
+ return a.length;
89
+ // Single-row DP — TS strict-array-access is happier than with a 2-D array,
90
+ // and the algorithm is identical (O(min(a, b)) memory).
91
+ let prev = [];
92
+ for (let j = 0; j <= b.length; j++)
93
+ prev.push(j);
94
+ for (let i = 1; i <= a.length; i++) {
95
+ const curr = [i];
96
+ for (let j = 1; j <= b.length; j++) {
97
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
98
+ curr.push(Math.min((prev[j] ?? 0) + 1, (curr[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost));
99
+ }
100
+ prev = curr;
101
+ }
102
+ return prev[b.length] ?? 0;
103
+ }
104
+ function suggestFlag(unknown, allowed) {
105
+ let best = null;
106
+ for (const f of allowed) {
107
+ const d = editDistance(unknown, f);
108
+ if (best === null || d < best.dist)
109
+ best = { flag: f, dist: d };
110
+ }
111
+ // Only suggest if the edit distance is small enough to be plausible.
112
+ if (best && best.dist <= 3)
113
+ return best.flag;
114
+ return null;
115
+ }
116
+ /**
117
+ * Validate flags for a subcommand. Returns ok=true if every dash-prefixed
118
+ * argv item is recognized; otherwise ok=false with a "Unknown flag …" message
119
+ * including a "did you mean" suggestion when one is plausible.
120
+ *
121
+ * Subcommand-specific positional arguments (e.g. `authorize --token <r>`)
122
+ * should be filtered out by the caller before passing argv here.
123
+ */
124
+ function validateFlags(command, argv) {
125
+ const allowed = [...(ALLOWED_FLAGS_BY_COMMAND[command] ?? []), ...GLOBAL_FLAGS];
126
+ for (const arg of argv) {
127
+ if (!arg.startsWith('-'))
128
+ continue;
129
+ // Strip `--key=value` form to its `--key` for lookup.
130
+ const flagName = arg.includes('=') ? arg.slice(0, arg.indexOf('=')) : arg;
131
+ if (allowed.includes(flagName))
132
+ continue;
133
+ const suggestion = suggestFlag(flagName, allowed);
134
+ const tail = suggestion
135
+ ? ` Did you mean ${suggestion}?`
136
+ : ` Run \`clien-mcp help\` for the supported flags.`;
137
+ return {
138
+ ok: false,
139
+ message: `Unknown flag: ${flagName}.${tail}`,
140
+ };
141
+ }
142
+ return { ok: true };
143
+ }
144
+ // ---------- Error formatting ----------
145
+ function formatFatal(err, debug) {
146
+ const message = err instanceof Error ? err.message : String(err);
147
+ if (debug && err instanceof Error && err.stack) {
148
+ return `error: ${message}\n${err.stack}`;
149
+ }
150
+ return `error: ${message}`;
151
+ }
152
+ /**
153
+ * Match a boolean flag in argv, accepting both `--flag` and `--flag=true`/
154
+ * `--flag=1` forms. The `=value` form is what `validateFlags` permits via
155
+ * its `--key=value` strip-to-`--key` lookup; without this helper, the
156
+ * dispatcher's `argv.includes('--flag')` would silently miss it.
157
+ */
158
+ function hasFlag(argv, name) {
159
+ for (const arg of argv) {
160
+ if (arg === name)
161
+ return true;
162
+ if (arg.startsWith(`${name}=`)) {
163
+ const value = arg.slice(name.length + 1).toLowerCase();
164
+ // `--flag=` (empty), `--flag=true`, `--flag=1` -> true.
165
+ // `--flag=false`, `--flag=0` -> false.
166
+ if (value === 'false' || value === '0')
167
+ return false;
168
+ return true;
169
+ }
170
+ }
171
+ return false;
172
+ }
173
+ // ---------- Main dispatcher ----------
174
+ export async function main(argv) {
175
+ void argv; // `--debug` is read by the outer isMainModule entrypoint; main() doesn't render fatals itself.
176
+ const cmd = argv[0] ?? '';
177
+ const isHelp = cmd === 'help' || cmd === '-h' || cmd === '--help';
178
+ const isVersion = cmd === 'version' || cmd === '-v' || cmd === '--version';
179
+ // Validate flags before the dispatch table. Don't apply to subcommands that
180
+ // accept positional values we don't yet model here (none today; placeholder).
181
+ const validation = validateFlags(isHelp ? 'help' : isVersion ? 'version' : cmd, argv.slice(1));
182
+ if (!validation.ok) {
183
+ process.stderr.write(`${validation.message}\n`);
184
+ return EXIT_CODES.INVALID_ARGS;
185
+ }
186
+ switch (cmd) {
187
+ case '':
188
+ case 'serve':
189
+ case 'server':
190
+ case 'start': {
191
+ await startStdioServer();
192
+ // stdio server runs until stdin closes — we never return here.
193
+ return EXIT_CODES.SUCCESS;
194
+ }
195
+ case 'install': {
196
+ const noBrowser = hasFlag(argv, '--no-browser');
197
+ const json = hasFlag(argv, '--json');
198
+ // --json implies non-interactive (agents pipe to jq); always suppress
199
+ // the browser open so JSON stdout isn't intermixed with a launcher.
200
+ await runInstall({ openBrowser: !noBrowser && !json, json });
201
+ return EXIT_CODES.SUCCESS;
202
+ }
203
+ case 'logout': {
204
+ const json = hasFlag(argv, '--json');
205
+ await runLogout({ json });
206
+ return EXIT_CODES.SUCCESS;
207
+ }
208
+ case 'status': {
209
+ const json = hasFlag(argv, '--json');
210
+ await runStatus({ json });
211
+ return EXIT_CODES.SUCCESS;
212
+ }
213
+ case 'authorize': {
214
+ // Accept `--token VALUE` and `--token=VALUE` forms.
215
+ let token;
216
+ for (let i = 1; i < argv.length; i++) {
217
+ const arg = argv[i];
218
+ if (arg === '--token' || arg === '-t') {
219
+ token = argv[i + 1];
220
+ i++;
221
+ }
222
+ else if (arg && arg.startsWith('--token=')) {
223
+ token = arg.slice('--token='.length);
224
+ }
225
+ }
226
+ try {
227
+ await runAuthorize({ token });
228
+ return 0;
229
+ }
230
+ catch (err) {
231
+ // `runAuthorize` writes its own stderr for the two expected paths
232
+ // (missing token, Supabase rejected token). Anything else — config
233
+ // load failure, save backend failure, unexpected throw — would
234
+ // exit silently with code 4 unless we surface it here.
235
+ const message = err instanceof Error ? err.message : String(err);
236
+ if (!/missing refresh token|refresh token rejected/i.test(message)) {
237
+ process.stderr.write(`error: ${message}\n`);
238
+ }
239
+ return 4;
240
+ }
241
+ }
242
+ case 'help':
243
+ case '-h':
244
+ case '--help':
245
+ printUsage();
246
+ return EXIT_CODES.SUCCESS;
247
+ case 'version':
248
+ case '-v':
249
+ case '--version':
250
+ process.stdout.write(`${PACKAGE_VERSION}\n`);
251
+ return EXIT_CODES.SUCCESS;
252
+ default:
253
+ process.stderr.write(`Unknown command: ${cmd}\n\n`);
254
+ printUsage(process.stderr);
255
+ return EXIT_CODES.INVALID_ARGS;
256
+ }
257
+ }
258
+ // Test helper: lets test files invoke the dispatcher without exiting the
259
+ // process. Production runs go through the unconditional invocation below.
260
+ export const __testing = { main, validateFlags, formatFatal, classifyError };
261
+ /* istanbul ignore next */
262
+ const isMainModule = typeof process !== 'undefined' &&
263
+ process.argv[1] !== undefined &&
264
+ // ts-node / tsx may put the source path here; check both .ts and .js endings.
265
+ /cli\.[mc]?[jt]sx?$/.test(process.argv[1]);
266
+ /* istanbul ignore next */
267
+ if (isMainModule) {
268
+ const argv = process.argv.slice(2);
269
+ const debug = argv.includes('--debug');
270
+ main(argv)
271
+ .then((code) => {
272
+ if (code !== 0)
273
+ process.exit(code);
274
+ })
275
+ .catch((err) => {
276
+ process.stderr.write(`${formatFatal(err, debug)}\n`);
277
+ process.exit(classifyError(err));
278
+ });
279
+ }
280
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `clien-mcp authorize --token <refresh>` — paste-mode auth for headless
3
+ * environments (devcontainers, CI runners, remote SSH) where the user can't
4
+ * complete a browser OAuth flow but CAN obtain a refresh token from a
5
+ * one-time browser session on their laptop. See issue #169.
6
+ *
7
+ * Validates the token by calling Supabase's refresh endpoint, then persists
8
+ * the rotated token via the same backend (keytar / encrypted file) the
9
+ * server uses. After this, subsequent MCP server starts skip OAuth entirely.
10
+ */
11
+ import { loadConfig } from '../config.js';
12
+ import { refreshAccessToken } from '../auth/oauth.js';
13
+ import { saveRefreshToken } from '../auth/storage.js';
14
+ /** Resolve the token from explicit option, then env var. */
15
+ function resolveToken(opts) {
16
+ if (opts.token && opts.token.length > 0)
17
+ return opts.token;
18
+ const fromEnv = process.env.CLIEN_MCP_REFRESH_TOKEN;
19
+ if (fromEnv && fromEnv.length > 0)
20
+ return fromEnv;
21
+ return null;
22
+ }
23
+ /**
24
+ * Run the authorize flow. Returns the storage backend used. Throws if no
25
+ * token is provided or the token is rejected by the auth server.
26
+ */
27
+ export async function runAuthorize(opts = {}) {
28
+ const stdout = opts.stdout ?? process.stdout;
29
+ const stderr = opts.stderr ?? process.stderr;
30
+ const token = resolveToken(opts);
31
+ if (!token) {
32
+ stderr.write('error: no refresh token provided. Pass --token <refresh> or set CLIEN_MCP_REFRESH_TOKEN.\n');
33
+ throw new Error('missing refresh token');
34
+ }
35
+ const config = loadConfig();
36
+ // Validate by performing a real refresh against Supabase. This catches
37
+ // typos, wrong-environment tokens, and revoked tokens up front instead of
38
+ // surfacing the failure in the middle of a tool call later.
39
+ let rotated;
40
+ try {
41
+ rotated = await refreshAccessToken(config, token);
42
+ }
43
+ catch (err) {
44
+ const msg = err instanceof Error ? err.message : String(err);
45
+ stderr.write(`error: refresh token rejected by auth server: ${msg}\n`);
46
+ throw err;
47
+ }
48
+ const backend = await saveRefreshToken(rotated.refreshToken);
49
+ stdout.write([
50
+ '',
51
+ ` ✓ Refresh token validated and saved (backend: ${backend}).`,
52
+ ' You can now run clien_research from non-interactive contexts.',
53
+ '',
54
+ ].join('\n'));
55
+ return { backend };
56
+ }
57
+ //# sourceMappingURL=authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/commands/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAcrD,4DAA4D;AAC5D,SAAS,YAAY,CAAC,IAAsB;IAC1C,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAA;IAC1D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAA;IACnD,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,OAAO,CAAA;IACjD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAyB,EAAE;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAA;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAA;IAE5C,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CACV,4FAA4F,CAC7F,CAAA;QACD,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAA;IAC1C,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAA;IAE3B,uEAAuE;IACvE,0EAA0E;IAC1E,4DAA4D;IAC5D,IAAI,OAAO,CAAA;IACX,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5D,MAAM,CAAC,KAAK,CAAC,iDAAiD,GAAG,IAAI,CAAC,CAAA;QACtE,MAAM,GAAG,CAAA;IACX,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;IAC5D,MAAM,CAAC,KAAK,CACV;QACE,EAAE;QACF,mDAAmD,OAAO,IAAI;QAC9D,iEAAiE;QACjE,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAA;IAED,OAAO,EAAE,OAAO,EAAE,CAAA;AACpB,CAAC"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * `npx @clien-ai/mcp install` — writes the clien entry into Claude Code's
3
+ * MCP server config so the user can skip the "copy/paste a JSON snippet" step.
4
+ *
5
+ * The flow:
6
+ * 1. Locate the Claude Code config file per-platform (default
7
+ * `~/.claude.json`, overridable via CLIEN_MCP_CLAUDE_CONFIG).
8
+ * 2. Read (or create) the file with `{ mcpServers: {} }`.
9
+ * 3. Merge `mcpServers.clien = { command: 'npx', args: ['-y', '@clien-ai/mcp'] }`.
10
+ * 4. Write atomically (via tmp file + rename).
11
+ * 5. Print next steps.
12
+ * 6. Optionally open the account page in the browser so the user is logged
13
+ * in before the first OAuth round-trip (`--no-browser` skips it).
14
+ *
15
+ * The install is idempotent: running it twice leaves the config unchanged.
16
+ */
17
+ import { existsSync, readFileSync, } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { join } from 'node:path';
20
+ import { writeFileAtomic } from '../util/atomic-write.js';
21
+ const ACCOUNT_URL = 'https://clien.ai/app/account';
22
+ const SERVER_KEY = 'clien';
23
+ /**
24
+ * Resolve the Claude Code MCP config path.
25
+ *
26
+ * Priority:
27
+ * 1. CLIEN_MCP_CLAUDE_CONFIG env var
28
+ * 2. Platform-default `~/.claude.json` (macOS/Linux) or `%USERPROFILE%\.claude.json` (Windows)
29
+ *
30
+ * (Claude Code stores MCP servers under `.mcpServers` in `~/.claude.json`
31
+ * — this is the stable cross-platform location as of 2026-04.)
32
+ */
33
+ export function resolveConfigPath(override) {
34
+ if (override && override.length > 0)
35
+ return override;
36
+ const fromEnv = process.env.CLIEN_MCP_CLAUDE_CONFIG;
37
+ if (fromEnv && fromEnv.length > 0)
38
+ return fromEnv;
39
+ return join(homedir(), '.claude.json');
40
+ }
41
+ function readConfig(path) {
42
+ if (!existsSync(path))
43
+ return {};
44
+ const raw = readFileSync(path, 'utf8').trim();
45
+ if (raw.length === 0)
46
+ return {};
47
+ try {
48
+ const parsed = JSON.parse(raw);
49
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
50
+ return parsed;
51
+ }
52
+ throw new Error('Config file did not contain a JSON object at the top level');
53
+ }
54
+ catch (err) {
55
+ throw new Error(`Failed to parse Claude Code config at ${path}: ${err.message}. ` +
56
+ `Fix or remove the file and re-run \`npx @clien-ai/mcp install\`.`);
57
+ }
58
+ }
59
+ function writeConfigAtomic(path, data) {
60
+ writeFileAtomic(path, `${JSON.stringify(data, null, 2)}\n`);
61
+ }
62
+ /** Build the mcpServers entry we add to Claude Code's config. */
63
+ function buildEntry(packageName) {
64
+ return {
65
+ command: 'npx',
66
+ args: ['-y', packageName],
67
+ };
68
+ }
69
+ function entriesMatch(a, b) {
70
+ if (!a || typeof a !== 'object')
71
+ return false;
72
+ const ax = a;
73
+ if (ax.command !== b.command)
74
+ return false;
75
+ if (!Array.isArray(ax.args) || ax.args.length !== b.args.length)
76
+ return false;
77
+ return ax.args.every((v, i) => v === b.args[i]);
78
+ }
79
+ export async function runInstall(opts = {}) {
80
+ const packageName = opts.packageName ?? '@clien-ai/mcp';
81
+ const configPath = resolveConfigPath(opts.configPath);
82
+ const stdout = opts.stdout ?? process.stdout;
83
+ const created = !existsSync(configPath);
84
+ const previous = readConfig(configPath);
85
+ const entry = buildEntry(packageName);
86
+ const mcpServers = { ...(previous.mcpServers ?? {}) };
87
+ const existing = mcpServers[SERVER_KEY];
88
+ const alreadyMatches = entriesMatch(existing, entry);
89
+ let updated = false;
90
+ if (!alreadyMatches) {
91
+ mcpServers[SERVER_KEY] = entry;
92
+ updated = true;
93
+ const next = { ...previous, mcpServers };
94
+ writeConfigAtomic(configPath, next);
95
+ }
96
+ if (opts.json) {
97
+ stdout.write(JSON.stringify({ configPath, created, updated }) + '\n');
98
+ }
99
+ else {
100
+ stdout.write([
101
+ '',
102
+ ` ✓ Clien.ai MCP ${updated ? (created ? 'written' : 'merged') : 'already present'} at ${configPath}`,
103
+ '',
104
+ ' Next steps:',
105
+ ' 1. Restart Claude Code to pick up the new server.',
106
+ ' 2. Ask Claude Code: "run a deep research pass on my product idea" —',
107
+ ' it will call `clien_research` and prompt you to log in via your browser.',
108
+ '',
109
+ ` We'll open ${ACCOUNT_URL} so you're logged in before the first OAuth handshake.`,
110
+ '',
111
+ ].join('\n'));
112
+ }
113
+ if (opts.openBrowser !== false) {
114
+ try {
115
+ const opener = opts.browserOpener ?? (await defaultBrowserOpener());
116
+ if (opener)
117
+ await opener(ACCOUNT_URL);
118
+ }
119
+ catch {
120
+ // Browser open is a nice-to-have — the user can click the URL above.
121
+ }
122
+ }
123
+ return {
124
+ configPath,
125
+ created,
126
+ updated,
127
+ previous,
128
+ };
129
+ }
130
+ async function defaultBrowserOpener() {
131
+ try {
132
+ const mod = (await import('open'));
133
+ const fn = typeof mod === 'function'
134
+ ? mod
135
+ : mod.default;
136
+ return async (url) => {
137
+ await fn(url);
138
+ };
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EACL,UAAU,EACV,YAAY,GACb,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAmCzD,MAAM,WAAW,GAAG,8BAA8B,CAAA;AAClD,MAAM,UAAU,GAAG,OAAO,CAAA;AAE1B;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAiB;IACjD,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAA;IACpD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAA;IACnD,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,OAAO,CAAA;IAEjD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAA;AACxC,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAA;IAChC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IAC7C,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAC/B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAA;QACzC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnE,OAAO,MAAuB,CAAA;QAChC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAA;IAC/E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,yCAAyC,IAAI,KAAM,GAAa,CAAC,OAAO,IAAI;YAC1E,kEAAkE,CACrE,CAAA;IACH,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY,EAAE,IAAmB;IAC1D,eAAe,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;AAC7D,CAAC;AAED,iEAAiE;AACjE,SAAS,UAAU,CAAC,WAAmB;IACrC,OAAO;QACL,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC;KAC1B,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,CAAU,EAAE,CAAsC;IACtE,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC7C,MAAM,EAAE,GAAG,CAA0C,CAAA;IACrD,IAAI,EAAE,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAC7E,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;AACjD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAuB,EAAE;IACxD,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,eAAe,CAAA;IACvD,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAA;IAC5C,MAAM,OAAO,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;IAEvC,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,CAAA;IACvC,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IAErC,MAAM,UAAU,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,UAAU,IAAI,EAAE,CAAC,EAA6B,CAAA;IAChF,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,CAAA;IACvC,MAAM,cAAc,GAAG,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;IAEpD,IAAI,OAAO,GAAG,KAAK,CAAA;IACnB,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,UAAU,CAAC,UAAU,CAAC,GAAG,KAAK,CAAA;QAC9B,OAAO,GAAG,IAAI,CAAA;QACd,MAAM,IAAI,GAAkB,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,CAAA;QACvD,iBAAiB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IACrC,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;IACvE,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,KAAK,CACV;YACE,EAAE;YACF,oBAAoB,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,iBAAiB,OAAO,UAAU,EAAE;YACrG,EAAE;YACF,eAAe;YACf,uDAAuD;YACvD,yEAAyE;YACzE,iFAAiF;YACjF,EAAE;YACF,gBAAgB,WAAW,wDAAwD;YACnF,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAA;IACH,CAAC;IAED,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,MAAM,oBAAoB,EAAE,CAAC,CAAA;YACnE,IAAI,MAAM;gBAAE,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;QACvE,CAAC;IACH,CAAC;IAED,OAAO;QACL,UAAU;QACV,OAAO;QACP,OAAO;QACP,QAAQ;KACT,CAAA;AACH,CAAC;AAED,KAAK,UAAU,oBAAoB;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,CAEM,CAAA;QACvC,MAAM,EAAE,GACN,OAAO,GAAG,KAAK,UAAU;YACvB,CAAC,CAAE,GAAyC;YAC5C,CAAC,CAAE,GAAsD,CAAC,OAAO,CAAA;QACrE,OAAO,KAAK,EAAE,GAAW,EAAE,EAAE;YAC3B,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA;QACf,CAAC,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `clien-mcp logout` — clear stored credentials so the next tool call
3
+ * triggers a fresh OAuth flow.
4
+ *
5
+ * Replaces the previous `node -e "require(...)"` workaround the README used
6
+ * to document. Agent-friendly: zero arguments, deterministic output, exits 0
7
+ * on success.
8
+ */
9
+ import { clearTokens, getStorageStatus } from '../auth/storage.js';
10
+ export async function runLogout(opts = {}) {
11
+ const stdout = opts.stdout ?? process.stdout;
12
+ const before = await getStorageStatus();
13
+ const hadTokenIn = before.hasToken ? before.backend : null;
14
+ await clearTokens();
15
+ if (opts.json) {
16
+ stdout.write(JSON.stringify({ ok: true, hadTokenIn }) + '\n');
17
+ }
18
+ else if (hadTokenIn) {
19
+ stdout.write([
20
+ '',
21
+ ` ✓ Cleared stored refresh token (was in ${hadTokenIn}).`,
22
+ ' Next clien_research call will trigger a fresh OAuth login.',
23
+ '',
24
+ ].join('\n'));
25
+ }
26
+ else {
27
+ stdout.write([
28
+ '',
29
+ ' No stored credentials found — already logged out.',
30
+ '',
31
+ ].join('\n'));
32
+ }
33
+ return { hadTokenIn };
34
+ }
35
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAalE,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAsB,EAAE;IACtD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAA;IAC5C,MAAM,MAAM,GAAG,MAAM,gBAAgB,EAAE,CAAA;IACvC,MAAM,UAAU,GAA6B,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;IAEpF,MAAM,WAAW,EAAE,CAAA;IAEnB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;IAC/D,CAAC;SAAM,IAAI,UAAU,EAAE,CAAC;QACtB,MAAM,CAAC,KAAK,CACV;YACE,EAAE;YACF,4CAA4C,UAAU,IAAI;YAC1D,8DAA8D;YAC9D,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAA;IACH,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,KAAK,CACV;YACE,EAAE;YACF,qDAAqD;YACrD,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAA;IACH,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,CAAA;AACvB,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `clien-mcp status` — print a diagnostic snapshot of the stored credentials.
3
+ *
4
+ * Used by agents and humans who want to confirm "am I logged in" before
5
+ * triggering a 25-minute research run. Outputs a machine-parseable JSON
6
+ * payload when `--json` is set, human-readable text otherwise.
7
+ *
8
+ * Note: token TTL (access-token expiry) isn't persisted on disk — only the
9
+ * refresh token is — so we surface what's actually knowable: backend, token
10
+ * presence, and the file's mtime when the encrypted-file backend is in use.
11
+ */
12
+ import { existsSync, statSync } from 'node:fs';
13
+ import { getStorageStatus, getTokenFilePath } from '../auth/storage.js';
14
+ import { PACKAGE_NAME, PACKAGE_VERSION } from '../config.js';
15
+ export async function runStatus(opts = {}) {
16
+ const stdout = opts.stdout ?? process.stdout;
17
+ const status = await getStorageStatus();
18
+ let tokenAgeMs = null;
19
+ let lastRefreshedAt = null;
20
+ if (status.backend === 'file' && status.hasToken) {
21
+ const path = getTokenFilePath();
22
+ if (existsSync(path)) {
23
+ try {
24
+ const stat = statSync(path);
25
+ tokenAgeMs = Math.max(0, Date.now() - stat.mtimeMs);
26
+ lastRefreshedAt = new Date(stat.mtimeMs).toISOString();
27
+ }
28
+ catch {
29
+ // benign — we just won't report mtime
30
+ }
31
+ }
32
+ }
33
+ const report = {
34
+ package: { name: PACKAGE_NAME, version: PACKAGE_VERSION },
35
+ backend: status.backend,
36
+ hasToken: status.hasToken,
37
+ tokenAgeMs,
38
+ lastRefreshedAt,
39
+ };
40
+ if (opts.json) {
41
+ stdout.write(JSON.stringify(report) + '\n');
42
+ return report;
43
+ }
44
+ const lines = ['', ` ${report.package.name} v${report.package.version}`, ''];
45
+ lines.push(` Backend: ${report.backend}`);
46
+ lines.push(` Has token: ${report.hasToken ? 'yes' : 'no'}`);
47
+ if (report.lastRefreshedAt) {
48
+ lines.push(` Last refreshed: ${report.lastRefreshedAt} (${humanizeAge(report.tokenAgeMs)})`);
49
+ }
50
+ else if (report.backend === 'keytar' && report.hasToken) {
51
+ lines.push(' Last refreshed: (metadata not exposed by OS keychain)');
52
+ }
53
+ lines.push('');
54
+ stdout.write(lines.join('\n'));
55
+ return report;
56
+ }
57
+ function humanizeAge(ms) {
58
+ const sec = Math.round(ms / 1000);
59
+ if (sec < 60)
60
+ return `${sec}s ago`;
61
+ const min = Math.round(sec / 60);
62
+ if (min < 60)
63
+ return `${min}m ago`;
64
+ const hr = Math.round(min / 60);
65
+ if (hr < 48)
66
+ return `${hr}h ago`;
67
+ const day = Math.round(hr / 24);
68
+ return `${day}d ago`;
69
+ }
70
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}