@simplysm/sd-claude 13.0.82 → 13.0.83

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 (51) hide show
  1. package/README.md +43 -0
  2. package/claude/skills/sd-plan/SKILL.md +4 -3
  3. package/package.json +3 -19
  4. package/scripts/postinstall.mjs +126 -9
  5. package/scripts/sd-entries.mjs +33 -0
  6. package/scripts/sync-claude-assets.mjs +4 -21
  7. package/dist/commands/auth-add.d.ts +0 -2
  8. package/dist/commands/auth-add.d.ts.map +0 -1
  9. package/dist/commands/auth-add.js +0 -32
  10. package/dist/commands/auth-add.js.map +0 -6
  11. package/dist/commands/auth-list.d.ts +0 -2
  12. package/dist/commands/auth-list.d.ts.map +0 -1
  13. package/dist/commands/auth-list.js +0 -95
  14. package/dist/commands/auth-list.js.map +0 -6
  15. package/dist/commands/auth-remove.d.ts +0 -2
  16. package/dist/commands/auth-remove.d.ts.map +0 -1
  17. package/dist/commands/auth-remove.js +0 -22
  18. package/dist/commands/auth-remove.js.map +0 -6
  19. package/dist/commands/auth-use.d.ts +0 -2
  20. package/dist/commands/auth-use.d.ts.map +0 -1
  21. package/dist/commands/auth-use.js +0 -33
  22. package/dist/commands/auth-use.js.map +0 -6
  23. package/dist/commands/auth-utils.d.ts +0 -11
  24. package/dist/commands/auth-utils.d.ts.map +0 -1
  25. package/dist/commands/auth-utils.js +0 -57
  26. package/dist/commands/auth-utils.js.map +0 -6
  27. package/dist/commands/install.d.ts +0 -2
  28. package/dist/commands/install.d.ts.map +0 -1
  29. package/dist/commands/install.js +0 -127
  30. package/dist/commands/install.js.map +0 -6
  31. package/dist/index.d.ts +0 -7
  32. package/dist/index.d.ts.map +0 -1
  33. package/dist/index.js +0 -7
  34. package/dist/index.js.map +0 -6
  35. package/dist/sd-claude.d.ts +0 -3
  36. package/dist/sd-claude.d.ts.map +0 -1
  37. package/dist/sd-claude.js +0 -78
  38. package/dist/sd-claude.js.map +0 -6
  39. package/src/commands/auth-add.ts +0 -36
  40. package/src/commands/auth-list.ts +0 -130
  41. package/src/commands/auth-remove.ts +0 -26
  42. package/src/commands/auth-use.ts +0 -53
  43. package/src/commands/auth-utils.ts +0 -65
  44. package/src/commands/install.ts +0 -183
  45. package/src/index.ts +0 -7
  46. package/src/sd-claude.ts +0 -98
  47. package/tests/auth-add.spec.ts +0 -74
  48. package/tests/auth-list.spec.ts +0 -198
  49. package/tests/auth-remove.spec.ts +0 -74
  50. package/tests/auth-use.spec.ts +0 -153
  51. package/tests/auth-utils.spec.ts +0 -173
@@ -1,153 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
2
- import path from "path";
3
- import fs from "fs";
4
- import os from "os";
5
- import { runAuthUse } from "../src/commands/auth-use";
6
-
7
- describe("runAuthUse", () => {
8
- let tmpDir: string;
9
-
10
- beforeEach(() => {
11
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "sd-claude-auth-use-test-"));
12
- });
13
-
14
- afterEach(() => {
15
- fs.rmSync(tmpDir, { recursive: true });
16
- vi.restoreAllMocks();
17
- });
18
-
19
- function setupProfile(name: string, opts?: { expiresAt?: number }): void {
20
- const profileDir = path.join(tmpDir, ".sd-claude", "auth", name);
21
- fs.mkdirSync(profileDir, { recursive: true });
22
-
23
- const authJson = {
24
- oauthAccount: { emailAddress: "work@example.com", token: "oauth-work" },
25
- userID: "user-work-456",
26
- };
27
- fs.writeFileSync(path.join(profileDir, "auth.json"), JSON.stringify(authJson));
28
-
29
- const credJson = {
30
- claudeAiOauth: {
31
- accessToken: "saved-access-token",
32
- refreshToken: "saved-refresh-token",
33
- expiresAt: opts?.expiresAt ?? Date.now() + 3_600_000,
34
- },
35
- };
36
- fs.writeFileSync(path.join(profileDir, "credentials.json"), JSON.stringify(credJson));
37
- }
38
-
39
- function setupClaudeJson(extra?: Record<string, unknown>): void {
40
- const claudeJson = {
41
- oauthAccount: { emailAddress: "old@example.com", token: "old-token" },
42
- userID: "user-old-111",
43
- someCustomSetting: "preserve-me",
44
- anotherField: 42,
45
- ...extra,
46
- };
47
- fs.writeFileSync(path.join(tmpDir, ".claude.json"), JSON.stringify(claudeJson));
48
- }
49
-
50
- function setupCredentialsJson(): void {
51
- const claudeDir = path.join(tmpDir, ".claude");
52
- fs.mkdirSync(claudeDir, { recursive: true });
53
- const credentials = { oldKey: "old-value", claudeAiOauth: { accessToken: "old-access" } };
54
- fs.writeFileSync(path.join(claudeDir, ".credentials.json"), JSON.stringify(credentials));
55
- }
56
-
57
- test("replaces oauthAccount and userID while preserving other fields in ~/.claude.json", () => {
58
- setupProfile("work");
59
- setupClaudeJson();
60
- setupCredentialsJson();
61
-
62
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
63
-
64
- runAuthUse("work", tmpDir);
65
-
66
- const result = JSON.parse(
67
- fs.readFileSync(path.join(tmpDir, ".claude.json"), "utf-8"),
68
- ) as Record<string, unknown>;
69
-
70
- // oauthAccount and userID should be replaced
71
- expect(result["oauthAccount"]).toEqual({
72
- emailAddress: "work@example.com",
73
- token: "oauth-work",
74
- });
75
- expect(result["userID"]).toBe("user-work-456");
76
-
77
- // Other fields must be preserved
78
- expect(result["someCustomSetting"]).toBe("preserve-me");
79
- expect(result["anotherField"]).toBe(42);
80
-
81
- logSpy.mockRestore();
82
- });
83
-
84
- test("replaces credentials.json entirely", () => {
85
- setupProfile("work");
86
- setupClaudeJson();
87
- setupCredentialsJson();
88
-
89
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
90
-
91
- runAuthUse("work", tmpDir);
92
-
93
- const result = JSON.parse(
94
- fs.readFileSync(path.join(tmpDir, ".claude", ".credentials.json"), "utf-8"),
95
- ) as Record<string, unknown>;
96
-
97
- // Should be entirely replaced with saved credentials (no oldKey)
98
- expect(result).toEqual({
99
- claudeAiOauth: {
100
- accessToken: "saved-access-token",
101
- refreshToken: "saved-refresh-token",
102
- expiresAt: expect.any(Number) as number,
103
- },
104
- });
105
- expect(result["oldKey"]).toBeUndefined();
106
-
107
- logSpy.mockRestore();
108
- });
109
-
110
- test("warns when token is expired (should not throw)", () => {
111
- const expiredTime = Date.now() - 1_000;
112
- setupProfile("work", { expiresAt: expiredTime });
113
- setupClaudeJson();
114
- setupCredentialsJson();
115
-
116
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
117
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
118
-
119
- // Should NOT throw
120
- expect(() => runAuthUse("work", tmpDir)).not.toThrow();
121
-
122
- // Should warn about expired token
123
- expect(warnSpy).toHaveBeenCalledWith("Warning: Token expired. Run /login after switching.");
124
-
125
- warnSpy.mockRestore();
126
- logSpy.mockRestore();
127
- });
128
-
129
- test("throws when profile not found", () => {
130
- setupClaudeJson();
131
- setupCredentialsJson();
132
-
133
- expect(() => runAuthUse("nonexistent", tmpDir)).toThrow("Profile 'nonexistent' not found.");
134
- });
135
-
136
- test("throws with invalid name", () => {
137
- expect(() => runAuthUse("BAD NAME!", tmpDir)).toThrow("Invalid name");
138
- });
139
-
140
- test("logs switched message with name and email", () => {
141
- setupProfile("work");
142
- setupClaudeJson();
143
- setupCredentialsJson();
144
-
145
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
146
-
147
- runAuthUse("work", tmpDir);
148
-
149
- expect(logSpy).toHaveBeenCalledWith("Switched to work (work@example.com)");
150
-
151
- logSpy.mockRestore();
152
- });
153
- });
@@ -1,173 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
- import path from "path";
3
- import fs from "fs";
4
- import os from "os";
5
- import {
6
- validateName,
7
- getProfileDir,
8
- profileExists,
9
- listProfiles,
10
- readCurrentAuth,
11
- readCurrentCredentials,
12
- getCurrentUserID,
13
- } from "../src/commands/auth-utils";
14
-
15
- describe("auth-utils", () => {
16
- let tmpDir: string;
17
-
18
- beforeEach(() => {
19
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "sd-claude-auth-test-"));
20
- });
21
-
22
- afterEach(() => {
23
- fs.rmSync(tmpDir, { recursive: true });
24
- });
25
-
26
- describe("validateName", () => {
27
- test("accepts lowercase letters", () => {
28
- expect(() => validateName("abc")).not.toThrow();
29
- });
30
-
31
- test("accepts digits", () => {
32
- expect(() => validateName("123")).not.toThrow();
33
- });
34
-
35
- test("accepts hyphens and underscores", () => {
36
- expect(() => validateName("my-profile_1")).not.toThrow();
37
- });
38
-
39
- test("rejects uppercase letters", () => {
40
- expect(() => validateName("ABC")).toThrow();
41
- });
42
-
43
- test("rejects spaces", () => {
44
- expect(() => validateName("my profile")).toThrow();
45
- });
46
-
47
- test("rejects special characters", () => {
48
- expect(() => validateName("my@profile")).toThrow();
49
- });
50
-
51
- test("rejects empty string", () => {
52
- expect(() => validateName("")).toThrow();
53
- });
54
-
55
- test("error message includes the invalid name", () => {
56
- expect(() => validateName("BAD!")).toThrow(/BAD!/);
57
- });
58
- });
59
-
60
- describe("getProfileDir", () => {
61
- test("returns correct path with homeDir", () => {
62
- const result = getProfileDir("work", tmpDir);
63
- expect(result).toBe(path.join(tmpDir, ".sd-claude", "auth", "work"));
64
- });
65
-
66
- test("uses os.homedir when homeDir is not provided", () => {
67
- const result = getProfileDir("test");
68
- expect(result).toBe(path.join(os.homedir(), ".sd-claude", "auth", "test"));
69
- });
70
- });
71
-
72
- describe("profileExists", () => {
73
- test("returns false when profile directory does not exist", () => {
74
- expect(profileExists("nonexistent", tmpDir)).toBe(false);
75
- });
76
-
77
- test("returns true when profile directory exists", () => {
78
- const profileDir = path.join(tmpDir, ".sd-claude", "auth", "work");
79
- fs.mkdirSync(profileDir, { recursive: true });
80
- expect(profileExists("work", tmpDir)).toBe(true);
81
- });
82
- });
83
-
84
- describe("listProfiles", () => {
85
- test("returns empty array when auth directory does not exist", () => {
86
- expect(listProfiles(tmpDir)).toEqual([]);
87
- });
88
-
89
- test("returns subdirectory names", () => {
90
- const authDir = path.join(tmpDir, ".sd-claude", "auth");
91
- fs.mkdirSync(path.join(authDir, "work"), { recursive: true });
92
- fs.mkdirSync(path.join(authDir, "personal"), { recursive: true });
93
-
94
- const result = listProfiles(tmpDir);
95
- expect(result.sort()).toEqual(["personal", "work"]);
96
- });
97
-
98
- test("ignores files in auth directory", () => {
99
- const authDir = path.join(tmpDir, ".sd-claude", "auth");
100
- fs.mkdirSync(authDir, { recursive: true });
101
- fs.writeFileSync(path.join(authDir, "some-file.txt"), "data");
102
- fs.mkdirSync(path.join(authDir, "profile1"), { recursive: true });
103
-
104
- expect(listProfiles(tmpDir)).toEqual(["profile1"]);
105
- });
106
- });
107
-
108
- describe("readCurrentAuth", () => {
109
- test("reads oauthAccount and userID from .claude.json", () => {
110
- const claudeJson = {
111
- oauthAccount: { email: "test@example.com", token: "abc" },
112
- userID: "user-123",
113
- };
114
- fs.writeFileSync(path.join(tmpDir, ".claude.json"), JSON.stringify(claudeJson));
115
-
116
- const result = readCurrentAuth(tmpDir);
117
- expect(result.oauthAccount).toEqual({ email: "test@example.com", token: "abc" });
118
- expect(result.userID).toBe("user-123");
119
- });
120
-
121
- test("throws when .claude.json does not exist", () => {
122
- expect(() => readCurrentAuth(tmpDir)).toThrow();
123
- });
124
-
125
- test("throws when oauthAccount is missing", () => {
126
- fs.writeFileSync(path.join(tmpDir, ".claude.json"), JSON.stringify({ userID: "user-123" }));
127
- expect(() => readCurrentAuth(tmpDir)).toThrow("Not logged in");
128
- });
129
-
130
- test("throws when userID is missing", () => {
131
- fs.writeFileSync(
132
- path.join(tmpDir, ".claude.json"),
133
- JSON.stringify({ oauthAccount: { email: "test@example.com" } }),
134
- );
135
- expect(() => readCurrentAuth(tmpDir)).toThrow("Not logged in");
136
- });
137
- });
138
-
139
- describe("readCurrentCredentials", () => {
140
- test("reads credentials from .claude/.credentials.json", () => {
141
- const credentials = { key: "value", secret: "s3cret" };
142
- const claudeDir = path.join(tmpDir, ".claude");
143
- fs.mkdirSync(claudeDir, { recursive: true });
144
- fs.writeFileSync(path.join(claudeDir, ".credentials.json"), JSON.stringify(credentials));
145
-
146
- const result = readCurrentCredentials(tmpDir);
147
- expect(result).toEqual({ key: "value", secret: "s3cret" });
148
- });
149
-
150
- test("throws when .credentials.json does not exist", () => {
151
- expect(() => readCurrentCredentials(tmpDir)).toThrow();
152
- });
153
- });
154
-
155
- describe("getCurrentUserID", () => {
156
- test("returns userID from .claude.json", () => {
157
- fs.writeFileSync(path.join(tmpDir, ".claude.json"), JSON.stringify({ userID: "user-456" }));
158
- expect(getCurrentUserID(tmpDir)).toBe("user-456");
159
- });
160
-
161
- test("returns undefined when .claude.json does not exist", () => {
162
- expect(getCurrentUserID(tmpDir)).toBeUndefined();
163
- });
164
-
165
- test("returns undefined when userID is missing from .claude.json", () => {
166
- fs.writeFileSync(
167
- path.join(tmpDir, ".claude.json"),
168
- JSON.stringify({ someOtherField: "value" }),
169
- );
170
- expect(getCurrentUserID(tmpDir)).toBeUndefined();
171
- });
172
- });
173
- });