@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.
Files changed (43) hide show
  1. package/README.md +65 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/commands/index.d.ts +3 -0
  4. package/dist/src/commands/index.d.ts.map +1 -1
  5. package/dist/src/commands/index.js +4 -0
  6. package/dist/src/commands/index.js.map +1 -1
  7. package/dist/src/commands/login.d.ts +23 -0
  8. package/dist/src/commands/login.d.ts.map +1 -0
  9. package/dist/src/commands/login.js +121 -0
  10. package/dist/src/commands/login.js.map +1 -0
  11. package/dist/src/commands/login.test.d.ts +8 -0
  12. package/dist/src/commands/login.test.d.ts.map +1 -0
  13. package/dist/src/commands/login.test.js +218 -0
  14. package/dist/src/commands/login.test.js.map +1 -0
  15. package/dist/src/commands/logout.d.ts +14 -0
  16. package/dist/src/commands/logout.d.ts.map +1 -0
  17. package/dist/src/commands/logout.js +45 -0
  18. package/dist/src/commands/logout.js.map +1 -0
  19. package/dist/src/commands/logout.test.d.ts +8 -0
  20. package/dist/src/commands/logout.test.d.ts.map +1 -0
  21. package/dist/src/commands/logout.test.js +135 -0
  22. package/dist/src/commands/logout.test.js.map +1 -0
  23. package/dist/src/commands/manage.d.ts +1 -16
  24. package/dist/src/commands/manage.d.ts.map +1 -1
  25. package/dist/src/commands/manage.js +3 -97
  26. package/dist/src/commands/manage.js.map +1 -1
  27. package/dist/src/commands/whoami.d.ts +14 -0
  28. package/dist/src/commands/whoami.d.ts.map +1 -0
  29. package/dist/src/commands/whoami.js +42 -0
  30. package/dist/src/commands/whoami.js.map +1 -0
  31. package/dist/src/commands/whoami.test.d.ts +8 -0
  32. package/dist/src/commands/whoami.test.d.ts.map +1 -0
  33. package/dist/src/commands/whoami.test.js +131 -0
  34. package/dist/src/commands/whoami.test.js.map +1 -0
  35. package/dist/src/index.js +12 -4
  36. package/dist/src/index.js.map +1 -1
  37. package/dist/src/templates/mcp-template-handlers.js +1 -1
  38. package/dist/src/templates/mcp-template-handlers.js.map +1 -1
  39. package/dist/src/utils/skills-directory.d.ts +25 -0
  40. package/dist/src/utils/skills-directory.d.ts.map +1 -0
  41. package/dist/src/utils/skills-directory.js +102 -0
  42. package/dist/src/utils/skills-directory.js.map +1 -0
  43. 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,8 @@
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
+ export {};
8
+ //# sourceMappingURL=login.test.d.ts.map
@@ -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,8 @@
1
+ /**
2
+ * SMI-2715: Logout Command Tests
3
+ *
4
+ * Tests for `skillsmith logout` — not-authenticated guard, confirmation prompt,
5
+ * successful logout, and partial failure (keyring error) handling.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=logout.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.test.d.ts","sourceRoot":"","sources":["../../../src/commands/logout.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}