@skillsmith/cli 0.3.8 → 0.4.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 +65 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/commands/index.d.ts +3 -0
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +4 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/login.d.ts +23 -0
- package/dist/src/commands/login.d.ts.map +1 -0
- package/dist/src/commands/login.js +121 -0
- package/dist/src/commands/login.js.map +1 -0
- package/dist/src/commands/login.test.d.ts +8 -0
- package/dist/src/commands/login.test.d.ts.map +1 -0
- package/dist/src/commands/login.test.js +218 -0
- package/dist/src/commands/login.test.js.map +1 -0
- package/dist/src/commands/logout.d.ts +14 -0
- package/dist/src/commands/logout.d.ts.map +1 -0
- package/dist/src/commands/logout.js +45 -0
- package/dist/src/commands/logout.js.map +1 -0
- package/dist/src/commands/logout.test.d.ts +8 -0
- package/dist/src/commands/logout.test.d.ts.map +1 -0
- package/dist/src/commands/logout.test.js +135 -0
- package/dist/src/commands/logout.test.js.map +1 -0
- package/dist/src/commands/manage.d.ts +1 -16
- package/dist/src/commands/manage.d.ts.map +1 -1
- package/dist/src/commands/manage.js +3 -97
- package/dist/src/commands/manage.js.map +1 -1
- package/dist/src/commands/whoami.d.ts +14 -0
- package/dist/src/commands/whoami.d.ts.map +1 -0
- package/dist/src/commands/whoami.js +42 -0
- package/dist/src/commands/whoami.js.map +1 -0
- package/dist/src/commands/whoami.test.d.ts +8 -0
- package/dist/src/commands/whoami.test.d.ts.map +1 -0
- package/dist/src/commands/whoami.test.js +131 -0
- package/dist/src/commands/whoami.test.js.map +1 -0
- package/dist/src/index.js +12 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/templates/mcp-template-handlers.js +1 -1
- package/dist/src/templates/mcp-template-handlers.js.map +1 -1
- package/dist/src/utils/skills-directory.d.ts +25 -0
- package/dist/src/utils/skills-directory.d.ts.map +1 -0
- package/dist/src/utils/skills-directory.js +102 -0
- package/dist/src/utils/skills-directory.js.map +1 -0
- package/package.json +7 -3
|
@@ -11,4 +11,7 @@ export { createRecommendCommand } from './recommend.js';
|
|
|
11
11
|
export { createSyncCommand } from './sync.js';
|
|
12
12
|
export { createMergeCommand } from './merge.js';
|
|
13
13
|
export { createInstallSkillCommand } from './install-skill.js';
|
|
14
|
+
export { createLoginCommand } from './login.js';
|
|
15
|
+
export { createLogoutCommand } from './logout.js';
|
|
16
|
+
export { createWhoamiCommand } from './whoami.js';
|
|
14
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAGjD,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAMzF,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAGnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAGvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAG7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAG/C,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAGjD,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAMzF,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAGnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAGvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAG7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAG/C,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA;AAG9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -22,4 +22,8 @@ export { createSyncCommand } from './sync.js';
|
|
|
22
22
|
export { createMergeCommand } from './merge.js';
|
|
23
23
|
// SMI-824: Install Skillsmith Skill Command
|
|
24
24
|
export { createInstallSkillCommand } from './install-skill.js';
|
|
25
|
+
// SMI-2715: CLI Login Device Flow
|
|
26
|
+
export { createLoginCommand } from './login.js';
|
|
27
|
+
export { createLogoutCommand } from './logout.js';
|
|
28
|
+
export { createWhoamiCommand } from './whoami.js';
|
|
25
29
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8BAA8B;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,4BAA4B;AAC5B,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEzF,2BAA2B;AAC3B,0CAA0C;AAC1C,mCAAmC;AACnC,8CAA8C;AAC9C,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAE1B,8BAA8B;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAEnD,4BAA4B;AAC5B,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAEvD,gBAAgB;AAChB,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAE7C,mCAAmC;AACnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,4CAA4C;AAC5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8BAA8B;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,4BAA4B;AAC5B,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEzF,2BAA2B;AAC3B,0CAA0C;AAC1C,mCAAmC;AACnC,8CAA8C;AAC9C,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAE1B,8BAA8B;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAEnD,4BAA4B;AAC5B,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAEvD,gBAAgB;AAChB,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAE7C,mCAAmC;AACnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,4CAA4C;AAC5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA;AAE9D,kCAAkC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login Command - Authenticate the Skillsmith CLI
|
|
3
|
+
*
|
|
4
|
+
* SMI-2715: CLI Login Device Flow
|
|
5
|
+
*
|
|
6
|
+
* Opens the Skillsmith dashboard in the browser (or prints the URL for
|
|
7
|
+
* headless/CI environments), prompts for an API key with masked input,
|
|
8
|
+
* validates the format, and stores it securely via @skillsmith/core.
|
|
9
|
+
*
|
|
10
|
+
* Security notes:
|
|
11
|
+
* - No --key <value> flag: would expose the secret in ps aux / shell history
|
|
12
|
+
* - Headless path: SKILLSMITH_API_KEY env var (documented in README)
|
|
13
|
+
* - Always-masked input: @inquirer/prompts password() renders bullets
|
|
14
|
+
*/
|
|
15
|
+
import { Command } from 'commander';
|
|
16
|
+
/**
|
|
17
|
+
* Create the `skillsmith login` command.
|
|
18
|
+
*
|
|
19
|
+
* Options:
|
|
20
|
+
* --no-browser Print URL instead of opening browser (for CI/headless)
|
|
21
|
+
*/
|
|
22
|
+
export declare function createLoginCommand(): Command;
|
|
23
|
+
//# sourceMappingURL=login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA8BnC;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAwF5C"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login Command - Authenticate the Skillsmith CLI
|
|
3
|
+
*
|
|
4
|
+
* SMI-2715: CLI Login Device Flow
|
|
5
|
+
*
|
|
6
|
+
* Opens the Skillsmith dashboard in the browser (or prints the URL for
|
|
7
|
+
* headless/CI environments), prompts for an API key with masked input,
|
|
8
|
+
* validates the format, and stores it securely via @skillsmith/core.
|
|
9
|
+
*
|
|
10
|
+
* Security notes:
|
|
11
|
+
* - No --key <value> flag: would expose the secret in ps aux / shell history
|
|
12
|
+
* - Headless path: SKILLSMITH_API_KEY env var (documented in README)
|
|
13
|
+
* - Always-masked input: @inquirer/prompts password() renders bullets
|
|
14
|
+
*/
|
|
15
|
+
import { Command } from 'commander';
|
|
16
|
+
import { password } from '@inquirer/prompts';
|
|
17
|
+
import chalk from 'chalk';
|
|
18
|
+
import { getAuthStatus, storeApiKey, isValidApiKeyFormat } from '@skillsmith/core';
|
|
19
|
+
/** URL where users generate their CLI API token */
|
|
20
|
+
const CLI_TOKEN_URL = 'https://skillsmith.app/account/cli-token';
|
|
21
|
+
/**
|
|
22
|
+
* Detect whether the current environment is headless (no interactive display).
|
|
23
|
+
* On Linux without DISPLAY, opening a browser will fail silently.
|
|
24
|
+
*/
|
|
25
|
+
function isHeadlessEnvironment() {
|
|
26
|
+
return process.env['CI'] === 'true' || (process.platform === 'linux' && !process.env['DISPLAY']);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Attempt to open a URL in the default browser.
|
|
30
|
+
* Returns true if the browser was opened, false otherwise.
|
|
31
|
+
*/
|
|
32
|
+
async function tryOpenBrowser(url) {
|
|
33
|
+
try {
|
|
34
|
+
const { default: open } = await import('open');
|
|
35
|
+
await open(url);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create the `skillsmith login` command.
|
|
44
|
+
*
|
|
45
|
+
* Options:
|
|
46
|
+
* --no-browser Print URL instead of opening browser (for CI/headless)
|
|
47
|
+
*/
|
|
48
|
+
export function createLoginCommand() {
|
|
49
|
+
return new Command('login')
|
|
50
|
+
.description('Authenticate the Skillsmith CLI with your API key')
|
|
51
|
+
.option('--no-browser', 'Print URL instead of opening browser (for CI/headless)')
|
|
52
|
+
.action(async (options) => {
|
|
53
|
+
// 1. Check if already authenticated
|
|
54
|
+
const status = await getAuthStatus();
|
|
55
|
+
if (status.authenticated) {
|
|
56
|
+
console.log(`Already authenticated (${status.keyPrefix}...). ` +
|
|
57
|
+
`Run \`skillsmith logout\` first to switch accounts.`);
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
// 2. Open browser or print URL
|
|
61
|
+
const headless = isHeadlessEnvironment();
|
|
62
|
+
const shouldOpenBrowser = options.browser && !headless;
|
|
63
|
+
if (shouldOpenBrowser) {
|
|
64
|
+
const opened = await tryOpenBrowser(CLI_TOKEN_URL);
|
|
65
|
+
if (opened) {
|
|
66
|
+
console.log(`Opening ${chalk.cyan(CLI_TOKEN_URL)} in your browser...`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(`Could not open browser. Visit this URL manually:`);
|
|
70
|
+
console.log(chalk.cyan(CLI_TOKEN_URL));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.log(`Visit this URL to get your API key:`);
|
|
75
|
+
console.log(chalk.cyan(CLI_TOKEN_URL));
|
|
76
|
+
}
|
|
77
|
+
// 3. Prompt for masked API key (up to 3 format-validation failures)
|
|
78
|
+
console.log('\nAfter authenticating, copy the API key shown and paste it below.');
|
|
79
|
+
let attempts = 0;
|
|
80
|
+
const MAX_ATTEMPTS = 3;
|
|
81
|
+
try {
|
|
82
|
+
while (attempts < MAX_ATTEMPTS) {
|
|
83
|
+
const raw = await password({ message: 'Paste your API key:' });
|
|
84
|
+
if (!isValidApiKeyFormat(raw)) {
|
|
85
|
+
attempts++;
|
|
86
|
+
if (attempts < MAX_ATTEMPTS) {
|
|
87
|
+
console.error(chalk.red(`That doesn't look like a valid key (expected: sk_live_...). ` +
|
|
88
|
+
`Try again (${attempts} of ${MAX_ATTEMPTS}).`));
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Valid format — store and exit
|
|
93
|
+
try {
|
|
94
|
+
await storeApiKey(raw);
|
|
95
|
+
}
|
|
96
|
+
catch (storeErr) {
|
|
97
|
+
console.error(chalk.red('\nFailed to store credentials: ' +
|
|
98
|
+
(storeErr instanceof Error ? storeErr.message : String(storeErr))));
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
console.log(chalk.green('\nLogged in successfully.'));
|
|
102
|
+
console.log(chalk.dim(' Note: your API key may still be in your clipboard. Clear it when done.'));
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (promptErr) {
|
|
107
|
+
// @inquirer/prompts throws ExitPromptError on Ctrl+C — exit cleanly
|
|
108
|
+
const name = promptErr.name;
|
|
109
|
+
if (name === 'ExitPromptError') {
|
|
110
|
+
console.log(chalk.dim('\nCancelled.'));
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
throw promptErr;
|
|
114
|
+
}
|
|
115
|
+
// Exhausted all attempts
|
|
116
|
+
console.error(chalk.red('\nToo many invalid attempts. Get a new key at:'));
|
|
117
|
+
console.error(chalk.cyan(CLI_TOKEN_URL));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../../src/commands/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.test.d.ts","sourceRoot":"","sources":["../../../src/commands/login.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2715: Login Command Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for `skillsmith login` — already-authenticated guard, browser/headless
|
|
5
|
+
* URL display, masked input prompting, format validation retries, and storage.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Mocks (declared before module imports so Vitest hoists them)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
vi.mock('@skillsmith/core', () => ({
|
|
12
|
+
getAuthStatus: vi.fn(),
|
|
13
|
+
storeApiKey: vi.fn(),
|
|
14
|
+
isValidApiKeyFormat: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
vi.mock('@inquirer/prompts', () => ({
|
|
17
|
+
password: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
vi.mock('open', () => ({
|
|
20
|
+
default: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Imports (after mocks)
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
import { createLoginCommand } from './login.js';
|
|
26
|
+
import { getAuthStatus, storeApiKey, isValidApiKeyFormat } from '@skillsmith/core';
|
|
27
|
+
import { password } from '@inquirer/prompts';
|
|
28
|
+
import openDefault from 'open';
|
|
29
|
+
const mockGetAuthStatus = vi.mocked(getAuthStatus);
|
|
30
|
+
const mockStoreApiKey = vi.mocked(storeApiKey);
|
|
31
|
+
const mockIsValidApiKeyFormat = vi.mocked(isValidApiKeyFormat);
|
|
32
|
+
const mockPassword = vi.mocked(password);
|
|
33
|
+
const mockOpen = vi.mocked(openDefault);
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Helpers
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
const VALID_KEY = 'sk_live_' + 'a'.repeat(32);
|
|
38
|
+
/** Run a command action by parsing fabricated argv */
|
|
39
|
+
async function runCommand(args = []) {
|
|
40
|
+
const cmd = createLoginCommand();
|
|
41
|
+
// Invoke action directly to bypass Commander's process.argv parsing
|
|
42
|
+
await cmd.parseAsync(['node', 'login', ...args]);
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Test suite
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
describe('createLoginCommand', () => {
|
|
48
|
+
let consoleLogSpy;
|
|
49
|
+
let consoleErrorSpy;
|
|
50
|
+
let processExitSpy;
|
|
51
|
+
let originalEnv;
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
originalEnv = { ...process.env };
|
|
54
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
55
|
+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
56
|
+
processExitSpy = vi
|
|
57
|
+
.spyOn(process, 'exit')
|
|
58
|
+
.mockImplementation((code) => {
|
|
59
|
+
throw new Error(`process.exit(${code ?? 0})`);
|
|
60
|
+
});
|
|
61
|
+
vi.clearAllMocks();
|
|
62
|
+
});
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
process.env = originalEnv;
|
|
65
|
+
consoleLogSpy.mockRestore();
|
|
66
|
+
consoleErrorSpy.mockRestore();
|
|
67
|
+
processExitSpy.mockRestore();
|
|
68
|
+
});
|
|
69
|
+
describe('command metadata', () => {
|
|
70
|
+
it('has the correct name', () => {
|
|
71
|
+
expect(createLoginCommand().name()).toBe('login');
|
|
72
|
+
});
|
|
73
|
+
it('has a description', () => {
|
|
74
|
+
expect(createLoginCommand().description()).toBeTruthy();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('already authenticated guard', () => {
|
|
78
|
+
it('exits 0 when already authenticated', async () => {
|
|
79
|
+
mockGetAuthStatus.mockResolvedValue({
|
|
80
|
+
authenticated: true,
|
|
81
|
+
keyPrefix: 'sk_live_xxxx',
|
|
82
|
+
source: 'keyring',
|
|
83
|
+
});
|
|
84
|
+
await expect(runCommand()).rejects.toThrow('process.exit(0)');
|
|
85
|
+
const output = consoleLogSpy.mock.calls.flat().join('\n');
|
|
86
|
+
expect(output).toContain('Already authenticated');
|
|
87
|
+
expect(output).toContain('skillsmith logout');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('URL display', () => {
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
mockGetAuthStatus.mockResolvedValue({
|
|
93
|
+
authenticated: false,
|
|
94
|
+
keyPrefix: null,
|
|
95
|
+
source: 'none',
|
|
96
|
+
});
|
|
97
|
+
// Default: valid key on first prompt attempt
|
|
98
|
+
mockIsValidApiKeyFormat.mockReturnValue(true);
|
|
99
|
+
mockPassword.mockResolvedValue(VALID_KEY);
|
|
100
|
+
mockStoreApiKey.mockResolvedValue(undefined);
|
|
101
|
+
});
|
|
102
|
+
it('opens browser by default (non-headless)', async () => {
|
|
103
|
+
delete process.env['CI'];
|
|
104
|
+
delete process.env['DISPLAY'];
|
|
105
|
+
// Simulate macOS (browser-capable)
|
|
106
|
+
const originalPlatform = process.platform;
|
|
107
|
+
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
|
|
108
|
+
mockOpen.mockResolvedValue(undefined);
|
|
109
|
+
try {
|
|
110
|
+
await expect(runCommand()).rejects.toThrow('process.exit(0)');
|
|
111
|
+
expect(mockOpen).toHaveBeenCalledWith('https://skillsmith.app/account/cli-token');
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
it('prints URL instead of opening browser when --no-browser is passed', async () => {
|
|
118
|
+
delete process.env['CI'];
|
|
119
|
+
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
|
|
120
|
+
await expect(runCommand(['--no-browser'])).rejects.toThrow('process.exit(0)');
|
|
121
|
+
expect(mockOpen).not.toHaveBeenCalled();
|
|
122
|
+
const output = consoleLogSpy.mock.calls.flat().join('\n');
|
|
123
|
+
expect(output).toContain('https://skillsmith.app/account/cli-token');
|
|
124
|
+
});
|
|
125
|
+
it('prints URL when CI=true (headless)', async () => {
|
|
126
|
+
process.env['CI'] = 'true';
|
|
127
|
+
await expect(runCommand()).rejects.toThrow('process.exit(0)');
|
|
128
|
+
expect(mockOpen).not.toHaveBeenCalled();
|
|
129
|
+
const output = consoleLogSpy.mock.calls.flat().join('\n');
|
|
130
|
+
expect(output).toContain('https://skillsmith.app/account/cli-token');
|
|
131
|
+
});
|
|
132
|
+
it('falls back to printing URL when open throws', async () => {
|
|
133
|
+
delete process.env['CI'];
|
|
134
|
+
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
|
|
135
|
+
mockOpen.mockRejectedValue(new Error('no browser'));
|
|
136
|
+
await expect(runCommand()).rejects.toThrow('process.exit(0)');
|
|
137
|
+
const output = consoleLogSpy.mock.calls.flat().join('\n');
|
|
138
|
+
expect(output).toContain('https://skillsmith.app/account/cli-token');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe('API key prompt', () => {
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
mockGetAuthStatus.mockResolvedValue({
|
|
144
|
+
authenticated: false,
|
|
145
|
+
keyPrefix: null,
|
|
146
|
+
source: 'none',
|
|
147
|
+
});
|
|
148
|
+
delete process.env['CI'];
|
|
149
|
+
process.env['CI'] = 'true'; // headless so we skip open
|
|
150
|
+
});
|
|
151
|
+
it('stores key and exits 0 on first valid input', async () => {
|
|
152
|
+
mockIsValidApiKeyFormat.mockReturnValue(true);
|
|
153
|
+
mockPassword.mockResolvedValue(VALID_KEY);
|
|
154
|
+
mockStoreApiKey.mockResolvedValue(undefined);
|
|
155
|
+
await expect(runCommand()).rejects.toThrow('process.exit(0)');
|
|
156
|
+
expect(mockStoreApiKey).toHaveBeenCalledWith(VALID_KEY);
|
|
157
|
+
const output = consoleLogSpy.mock.calls.flat().join('\n');
|
|
158
|
+
expect(output).toContain('Logged in successfully');
|
|
159
|
+
});
|
|
160
|
+
it('retries on invalid format and succeeds on second attempt', async () => {
|
|
161
|
+
mockIsValidApiKeyFormat.mockReturnValueOnce(false).mockReturnValueOnce(true);
|
|
162
|
+
mockPassword.mockResolvedValue(VALID_KEY);
|
|
163
|
+
mockStoreApiKey.mockResolvedValue(undefined);
|
|
164
|
+
await expect(runCommand()).rejects.toThrow('process.exit(0)');
|
|
165
|
+
expect(mockPassword).toHaveBeenCalledTimes(2);
|
|
166
|
+
expect(mockStoreApiKey).toHaveBeenCalledOnce();
|
|
167
|
+
});
|
|
168
|
+
it('exits 1 after 3 consecutive format failures', async () => {
|
|
169
|
+
mockIsValidApiKeyFormat.mockReturnValue(false);
|
|
170
|
+
mockPassword.mockResolvedValue('bad-key');
|
|
171
|
+
await expect(runCommand()).rejects.toThrow('process.exit(1)');
|
|
172
|
+
expect(mockPassword).toHaveBeenCalledTimes(3);
|
|
173
|
+
expect(mockStoreApiKey).not.toHaveBeenCalled();
|
|
174
|
+
const errorOutput = consoleErrorSpy.mock.calls.flat().join('\n');
|
|
175
|
+
expect(errorOutput).toContain('Too many invalid attempts');
|
|
176
|
+
expect(errorOutput).toContain('https://skillsmith.app/account/cli-token');
|
|
177
|
+
});
|
|
178
|
+
it('shows retry count message on invalid attempt (not the last)', async () => {
|
|
179
|
+
mockIsValidApiKeyFormat
|
|
180
|
+
.mockReturnValueOnce(false)
|
|
181
|
+
.mockReturnValueOnce(false)
|
|
182
|
+
.mockReturnValueOnce(false);
|
|
183
|
+
mockPassword.mockResolvedValue('bad-key');
|
|
184
|
+
await expect(runCommand()).rejects.toThrow('process.exit(1)');
|
|
185
|
+
const errorOutput = consoleErrorSpy.mock.calls.flat().join('\n');
|
|
186
|
+
// Should mention format guidance
|
|
187
|
+
expect(errorOutput).toContain('sk_live_');
|
|
188
|
+
});
|
|
189
|
+
it('reminds user to clear clipboard after successful login', async () => {
|
|
190
|
+
mockIsValidApiKeyFormat.mockReturnValue(true);
|
|
191
|
+
mockPassword.mockResolvedValue(VALID_KEY);
|
|
192
|
+
mockStoreApiKey.mockResolvedValue(undefined);
|
|
193
|
+
await expect(runCommand()).rejects.toThrow('process.exit(0)');
|
|
194
|
+
const output = consoleLogSpy.mock.calls.flat().join('\n');
|
|
195
|
+
expect(output).toContain('clipboard');
|
|
196
|
+
});
|
|
197
|
+
it('exits 1 with message when storeApiKey throws (filesystem error)', async () => {
|
|
198
|
+
mockIsValidApiKeyFormat.mockReturnValue(true);
|
|
199
|
+
mockPassword.mockResolvedValue(VALID_KEY);
|
|
200
|
+
mockStoreApiKey.mockRejectedValue(new Error('EACCES: permission denied'));
|
|
201
|
+
await expect(runCommand()).rejects.toThrow('process.exit(1)');
|
|
202
|
+
const errorOutput = consoleErrorSpy.mock.calls.flat().join('\n');
|
|
203
|
+
expect(errorOutput).toContain('Failed to store credentials');
|
|
204
|
+
expect(errorOutput).toContain('EACCES');
|
|
205
|
+
});
|
|
206
|
+
it('exits 0 cleanly on Ctrl+C (ExitPromptError)', async () => {
|
|
207
|
+
const exitPromptError = Object.assign(new Error('User force closed the prompt'), {
|
|
208
|
+
name: 'ExitPromptError',
|
|
209
|
+
});
|
|
210
|
+
mockPassword.mockRejectedValue(exitPromptError);
|
|
211
|
+
await expect(runCommand()).rejects.toThrow('process.exit(0)');
|
|
212
|
+
expect(mockStoreApiKey).not.toHaveBeenCalled();
|
|
213
|
+
const output = consoleLogSpy.mock.calls.flat().join('\n');
|
|
214
|
+
expect(output).toContain('Cancelled');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
//# sourceMappingURL=login.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.test.js","sourceRoot":"","sources":["../../../src/commands/login.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logout Command - Remove stored Skillsmith API key
|
|
3
|
+
*
|
|
4
|
+
* SMI-2715: CLI Login Device Flow
|
|
5
|
+
*
|
|
6
|
+
* Checks authentication status, prompts for confirmation, and clears
|
|
7
|
+
* the stored API key from all storage locations (keyring + config file).
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
/**
|
|
11
|
+
* Create the `skillsmith logout` command.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createLogoutCommand(): Command;
|
|
14
|
+
//# sourceMappingURL=logout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAKnC;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAmC7C"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logout Command - Remove stored Skillsmith API key
|
|
3
|
+
*
|
|
4
|
+
* SMI-2715: CLI Login Device Flow
|
|
5
|
+
*
|
|
6
|
+
* Checks authentication status, prompts for confirmation, and clears
|
|
7
|
+
* the stored API key from all storage locations (keyring + config file).
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
import { confirm } from '@inquirer/prompts';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { clearApiKey, getAuthStatus } from '@skillsmith/core';
|
|
13
|
+
/**
|
|
14
|
+
* Create the `skillsmith logout` command.
|
|
15
|
+
*/
|
|
16
|
+
export function createLogoutCommand() {
|
|
17
|
+
return new Command('logout').description('Remove stored Skillsmith API key').action(async () => {
|
|
18
|
+
// 1. Check whether there is anything to remove
|
|
19
|
+
const status = await getAuthStatus();
|
|
20
|
+
if (!status.authenticated) {
|
|
21
|
+
console.log('Not authenticated. Nothing to log out.');
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
// 2. Confirm before removing
|
|
25
|
+
const confirmed = await confirm({
|
|
26
|
+
message: 'Log out and remove stored API key?',
|
|
27
|
+
default: false,
|
|
28
|
+
});
|
|
29
|
+
if (!confirmed) {
|
|
30
|
+
console.log('Cancelled.');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
// 3. Clear key from all storage locations
|
|
34
|
+
const result = await clearApiKey();
|
|
35
|
+
if (result.success) {
|
|
36
|
+
console.log(chalk.green(`Logged out. Key removed from ${result.source}.`));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.log(chalk.yellow(`Logged out (config file cleared), but could not remove from keyring: ${result.error}`));
|
|
40
|
+
console.log(chalk.dim('The key may still be stored in your OS keyring.'));
|
|
41
|
+
}
|
|
42
|
+
process.exit(0);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# 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,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3C,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAE7D;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,kCAAkC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;QAC7F,+CAA+C;QAC/C,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAA;QACpC,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAA;YACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,6BAA6B;QAC7B,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC;YAC9B,OAAO,EAAE,oCAAoC;YAC7C,OAAO,EAAE,KAAK;SACf,CAAC,CAAA;QAEF,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,0CAA0C;QAC1C,MAAM,MAAM,GAAG,MAAM,WAAW,EAAE,CAAA;QAElC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,gCAAgC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QAC5E,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CACV,wEAAwE,MAAM,CAAC,KAAK,EAAE,CACvF,CACF,CAAA;YACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC,CAAA;QAC3E,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.test.d.ts","sourceRoot":"","sources":["../../../src/commands/logout.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|