repowise 0.1.5 → 0.1.6

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 (66) hide show
  1. package/dist/bin/repowise.js +23 -6
  2. package/dist/src/commands/create.d.ts +1 -1
  3. package/dist/src/commands/create.d.ts.map +1 -1
  4. package/dist/src/commands/create.js +237 -2
  5. package/dist/src/commands/create.js.map +1 -1
  6. package/dist/src/commands/listen.d.ts +3 -2
  7. package/dist/src/commands/listen.d.ts.map +1 -1
  8. package/dist/src/commands/listen.js +45 -3
  9. package/dist/src/commands/listen.js.map +1 -1
  10. package/dist/src/commands/login.d.ts +4 -1
  11. package/dist/src/commands/login.d.ts.map +1 -1
  12. package/dist/src/commands/login.js +56 -2
  13. package/dist/src/commands/login.js.map +1 -1
  14. package/dist/src/commands/logout.d.ts +1 -1
  15. package/dist/src/commands/logout.d.ts.map +1 -1
  16. package/dist/src/commands/logout.js +10 -2
  17. package/dist/src/commands/logout.js.map +1 -1
  18. package/dist/src/commands/start.d.ts +2 -0
  19. package/dist/src/commands/start.d.ts.map +1 -0
  20. package/dist/src/commands/start.js +17 -0
  21. package/dist/src/commands/start.js.map +1 -0
  22. package/dist/src/commands/status.d.ts +1 -1
  23. package/dist/src/commands/status.d.ts.map +1 -1
  24. package/dist/src/commands/status.js +61 -2
  25. package/dist/src/commands/status.js.map +1 -1
  26. package/dist/src/commands/stop.d.ts +2 -0
  27. package/dist/src/commands/stop.d.ts.map +1 -0
  28. package/dist/src/commands/stop.js +17 -0
  29. package/dist/src/commands/stop.js.map +1 -0
  30. package/dist/src/lib/ai-tools.d.ts +23 -0
  31. package/dist/src/lib/ai-tools.d.ts.map +1 -0
  32. package/dist/src/lib/ai-tools.js +193 -0
  33. package/dist/src/lib/ai-tools.js.map +1 -0
  34. package/dist/src/lib/api.d.ts.map +1 -1
  35. package/dist/src/lib/api.js +25 -4
  36. package/dist/src/lib/api.js.map +1 -1
  37. package/dist/src/lib/auth.d.ts +18 -0
  38. package/dist/src/lib/auth.d.ts.map +1 -1
  39. package/dist/src/lib/auth.js +251 -7
  40. package/dist/src/lib/auth.js.map +1 -1
  41. package/dist/src/lib/config.d.ts +2 -0
  42. package/dist/src/lib/config.d.ts.map +1 -1
  43. package/dist/src/lib/config.js.map +1 -1
  44. package/dist/src/lib/env.d.ts +10 -0
  45. package/dist/src/lib/env.d.ts.map +1 -0
  46. package/dist/src/lib/env.js +26 -0
  47. package/dist/src/lib/env.js.map +1 -0
  48. package/dist/src/lib/interview-handler.d.ts +2 -0
  49. package/dist/src/lib/interview-handler.d.ts.map +1 -0
  50. package/dist/src/lib/interview-handler.js +96 -0
  51. package/dist/src/lib/interview-handler.js.map +1 -0
  52. package/dist/src/lib/progress-renderer.d.ts +69 -0
  53. package/dist/src/lib/progress-renderer.d.ts.map +1 -0
  54. package/dist/src/lib/progress-renderer.js +186 -0
  55. package/dist/src/lib/progress-renderer.js.map +1 -0
  56. package/dist/src/lib/prompts.d.ts +6 -1
  57. package/dist/src/lib/prompts.d.ts.map +1 -1
  58. package/dist/src/lib/prompts.js +21 -3
  59. package/dist/src/lib/prompts.js.map +1 -1
  60. package/dist/src/types/index.d.ts +1 -0
  61. package/dist/src/types/index.d.ts.map +1 -1
  62. package/dist/tsup.config.d.ts +3 -0
  63. package/dist/tsup.config.d.ts.map +1 -0
  64. package/dist/tsup.config.js +18 -0
  65. package/dist/tsup.config.js.map +1 -0
  66. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop.d.ts","sourceRoot":"","sources":["../../../src/commands/stop.ts"],"names":[],"mappings":"AAAA,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAgB1C"}
@@ -0,0 +1,17 @@
1
+ export async function stop() {
2
+ try {
3
+ const { isRunning, stopProcess } = await import('@repowise/listener/process-manager');
4
+ if (!(await isRunning())) {
5
+ console.log('Listener is not running.');
6
+ return;
7
+ }
8
+ await stopProcess();
9
+ console.log('Listener stopped.');
10
+ }
11
+ catch (err) {
12
+ const message = err instanceof Error ? err.message : 'Unknown error';
13
+ console.error(`Failed to stop listener: ${message}`);
14
+ process.exitCode = 1;
15
+ }
16
+ }
17
+ //# sourceMappingURL=stop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop.js","sourceRoot":"","sources":["../../../src/commands/stop.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,IAAI,CAAC;QACH,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,oCAAoC,CAAC,CAAC;QAEtF,IAAI,CAAC,CAAC,MAAM,SAAS,EAAE,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;YACxC,OAAO;QACT,CAAC;QAED,MAAM,WAAW,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QACrE,OAAO,CAAC,KAAK,CAAC,4BAA4B,OAAO,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,23 @@
1
+ export type AiTool = 'cursor' | 'claude-code' | 'copilot' | 'windsurf' | 'cline' | 'codex' | 'roo-code';
2
+ export interface ContextFileInfo {
3
+ fileName: string;
4
+ relativePath: string;
5
+ }
6
+ interface ToolConfig {
7
+ label: string;
8
+ fileName: string;
9
+ filePath: string;
10
+ markerStart: string;
11
+ markerEnd: string;
12
+ format: 'markdown' | 'plain-text';
13
+ }
14
+ export declare const AI_TOOL_CONFIG: Record<AiTool, ToolConfig>;
15
+ export declare const SUPPORTED_TOOLS: AiTool[];
16
+ export declare function generateReference(tool: AiTool, repoName: string, contextFolder: string, contextFiles: ContextFileInfo[]): string;
17
+ export declare function updateToolConfig(repoRoot: string, tool: AiTool, repoName: string, contextFolder: string, contextFiles: ContextFileInfo[]): Promise<{
18
+ created: boolean;
19
+ }>;
20
+ export declare function removeToolConfig(repoRoot: string, tool: AiTool): Promise<void>;
21
+ export declare function scanLocalContextFiles(repoRoot: string, contextFolder: string): Promise<ContextFileInfo[]>;
22
+ export {};
23
+ //# sourceMappingURL=ai-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-tools.d.ts","sourceRoot":"","sources":["../../../src/lib/ai-tools.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,MAAM,GACd,QAAQ,GACR,aAAa,GACb,SAAS,GACT,UAAU,GACV,OAAO,GACP,OAAO,GACP,UAAU,CAAC;AAEf,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,GAAG,YAAY,CAAC;CACnC;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAyDrD,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAAM,EAA4C,CAAC;AAejF,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,eAAe,EAAE,GAC9B,MAAM,CA4DR;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,eAAe,EAAE,GAC9B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAuC/B;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBpF;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,eAAe,EAAE,CAAC,CAe5B"}
@@ -0,0 +1,193 @@
1
+ import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
2
+ import { join, dirname } from 'node:path';
3
+ export const AI_TOOL_CONFIG = {
4
+ cursor: {
5
+ label: 'Cursor',
6
+ fileName: '.cursorrules',
7
+ filePath: '.cursorrules',
8
+ markerStart: '# --- repowise-start ---',
9
+ markerEnd: '# --- repowise-end ---',
10
+ format: 'plain-text',
11
+ },
12
+ 'claude-code': {
13
+ label: 'Claude Code',
14
+ fileName: 'CLAUDE.md',
15
+ filePath: 'CLAUDE.md',
16
+ markerStart: '<!-- repowise-start -->',
17
+ markerEnd: '<!-- repowise-end -->',
18
+ format: 'markdown',
19
+ },
20
+ copilot: {
21
+ label: 'GitHub Copilot',
22
+ fileName: 'copilot-instructions.md',
23
+ filePath: '.github/copilot-instructions.md',
24
+ markerStart: '<!-- repowise-start -->',
25
+ markerEnd: '<!-- repowise-end -->',
26
+ format: 'markdown',
27
+ },
28
+ windsurf: {
29
+ label: 'Windsurf',
30
+ fileName: '.windsurfrules',
31
+ filePath: '.windsurfrules',
32
+ markerStart: '# --- repowise-start ---',
33
+ markerEnd: '# --- repowise-end ---',
34
+ format: 'plain-text',
35
+ },
36
+ cline: {
37
+ label: 'Cline',
38
+ fileName: '.clinerules',
39
+ filePath: '.clinerules',
40
+ markerStart: '# --- repowise-start ---',
41
+ markerEnd: '# --- repowise-end ---',
42
+ format: 'plain-text',
43
+ },
44
+ codex: {
45
+ label: 'Codex',
46
+ fileName: 'AGENTS.md',
47
+ filePath: 'AGENTS.md',
48
+ markerStart: '<!-- repowise-start -->',
49
+ markerEnd: '<!-- repowise-end -->',
50
+ format: 'markdown',
51
+ },
52
+ 'roo-code': {
53
+ label: 'Roo Code',
54
+ fileName: 'rules.md',
55
+ filePath: '.roo/rules.md',
56
+ markerStart: '<!-- repowise-start -->',
57
+ markerEnd: '<!-- repowise-end -->',
58
+ format: 'markdown',
59
+ },
60
+ };
61
+ export const SUPPORTED_TOOLS = Object.keys(AI_TOOL_CONFIG);
62
+ function sanitizeRepoName(name) {
63
+ // Strip markdown/HTML special chars to prevent content injection
64
+ return name.replace(/[<>[\]`()|\\]/g, '');
65
+ }
66
+ function fileDescriptionFromName(fileName) {
67
+ return fileName
68
+ .replace(/\.md$/, '')
69
+ .split('-')
70
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
71
+ .join(' ');
72
+ }
73
+ export function generateReference(tool, repoName, contextFolder, contextFiles) {
74
+ const config = AI_TOOL_CONFIG[tool];
75
+ const safeName = sanitizeRepoName(repoName);
76
+ const fileLines = contextFiles.map((f) => {
77
+ const desc = fileDescriptionFromName(f.fileName);
78
+ const isOverview = f.fileName === 'project-overview.md';
79
+ return { path: f.relativePath, desc: isOverview ? `${desc} (full index of all files)` : desc };
80
+ });
81
+ const hasFiles = fileLines.length > 0;
82
+ if (config.format === 'markdown') {
83
+ const lines = [
84
+ config.markerStart,
85
+ '',
86
+ `## Project Context \u2014 ${safeName}`,
87
+ '',
88
+ `This repository has AI-optimized context files generated by RepoWise.`,
89
+ `Before making changes, read the relevant context files in \`${contextFolder}/\` to understand the project's architecture, coding patterns, conventions, and domain knowledge.`,
90
+ '',
91
+ `**Start here:** \`${contextFolder}/project-overview.md\` \u2014 the routing document that describes every context file and when to read it.`,
92
+ '',
93
+ ];
94
+ if (hasFiles) {
95
+ lines.push(`**Core context files:**`, '', ...fileLines.map((f) => `- \`${f.path}\` \u2014 ${f.desc}`), '', `> Additional context files may exist beyond this list. Check \`project-overview.md\` for the complete index.`);
96
+ }
97
+ lines.push('', config.markerEnd);
98
+ return lines.join('\n');
99
+ }
100
+ // plain-text format
101
+ const lines = [
102
+ config.markerStart,
103
+ `# Project Context \u2014 ${safeName}`,
104
+ '#',
105
+ `# This repository has AI-optimized context files generated by RepoWise.`,
106
+ `# Before making changes, read the relevant context files in ${contextFolder}/`,
107
+ `# to understand the project's architecture, coding patterns, conventions, and domain knowledge.`,
108
+ '#',
109
+ `# Start here: ${contextFolder}/project-overview.md`,
110
+ `# The routing document that describes every context file and when to read it.`,
111
+ ];
112
+ if (hasFiles) {
113
+ lines.push('#', `# Core context files:`, ...fileLines.map((f) => `# ${f.path} \u2014 ${f.desc}`), '#', '# Additional context files may exist beyond this list.', '# Check project-overview.md for the complete index.');
114
+ }
115
+ lines.push(config.markerEnd);
116
+ return lines.join('\n');
117
+ }
118
+ export async function updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
119
+ const config = AI_TOOL_CONFIG[tool];
120
+ const fullPath = join(repoRoot, config.filePath);
121
+ // Ensure parent directory exists for tools in subdirectories
122
+ const dir = dirname(fullPath);
123
+ if (dir !== repoRoot) {
124
+ await mkdir(dir, { recursive: true });
125
+ }
126
+ const referenceBlock = generateReference(tool, repoName, contextFolder, contextFiles);
127
+ let existing = '';
128
+ let created = true;
129
+ try {
130
+ existing = await readFile(fullPath, 'utf-8');
131
+ created = false;
132
+ }
133
+ catch (err) {
134
+ if (err.code !== 'ENOENT')
135
+ throw err;
136
+ }
137
+ const startIdx = existing.indexOf(config.markerStart);
138
+ const endIdx = existing.indexOf(config.markerEnd);
139
+ let content;
140
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
141
+ // Replace between markers (inclusive)
142
+ const before = existing.slice(0, startIdx);
143
+ const after = existing.slice(endIdx + config.markerEnd.length);
144
+ content = before + referenceBlock + after;
145
+ }
146
+ else {
147
+ // Append to end
148
+ const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n\n' : existing.length > 0 ? '\n' : '';
149
+ content = existing + separator + referenceBlock + '\n';
150
+ }
151
+ await writeFile(fullPath, content, 'utf-8');
152
+ return { created };
153
+ }
154
+ export async function removeToolConfig(repoRoot, tool) {
155
+ const config = AI_TOOL_CONFIG[tool];
156
+ const fullPath = join(repoRoot, config.filePath);
157
+ let existing;
158
+ try {
159
+ existing = await readFile(fullPath, 'utf-8');
160
+ }
161
+ catch (err) {
162
+ if (err.code === 'ENOENT')
163
+ return;
164
+ throw err;
165
+ }
166
+ const startIdx = existing.indexOf(config.markerStart);
167
+ const endIdx = existing.indexOf(config.markerEnd);
168
+ if (startIdx === -1 || endIdx === -1)
169
+ return;
170
+ const before = existing.slice(0, startIdx);
171
+ const after = existing.slice(endIdx + config.markerEnd.length);
172
+ const content = (before + after).replace(/\n{3,}/g, '\n\n').trim() + '\n';
173
+ await writeFile(fullPath, content, 'utf-8');
174
+ }
175
+ export async function scanLocalContextFiles(repoRoot, contextFolder) {
176
+ const folderPath = join(repoRoot, contextFolder);
177
+ try {
178
+ const entries = await readdir(folderPath, { withFileTypes: true });
179
+ return entries
180
+ .filter((e) => e.isFile() && e.name.endsWith('.md'))
181
+ .map((e) => ({
182
+ fileName: e.name,
183
+ relativePath: `${contextFolder}/${e.name}`,
184
+ }))
185
+ .sort((a, b) => a.fileName.localeCompare(b.fileName));
186
+ }
187
+ catch (err) {
188
+ if (err.code === 'ENOENT')
189
+ return [];
190
+ throw err;
191
+ }
192
+ }
193
+ //# sourceMappingURL=ai-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-tools.js","sourceRoot":"","sources":["../../../src/lib/ai-tools.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"AAMA,wBAAsB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAgBnF"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"AAWA,wBAAsB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAkCnF"}
@@ -1,15 +1,36 @@
1
- const API_URL = process.env['REPOWISE_API_URL'] ?? 'https://api.repowise.ai';
1
+ import { clearCredentials, getValidCredentials } from './auth.js';
2
+ import { getEnvConfig } from './env.js';
3
+ function getApiUrl() {
4
+ return process.env['REPOWISE_API_URL'] ?? getEnvConfig().apiUrl;
5
+ }
2
6
  export async function apiRequest(path, options) {
3
- const response = await fetch(`${API_URL}${path}`, {
7
+ const credentials = await getValidCredentials();
8
+ if (!credentials) {
9
+ throw new Error('Not logged in. Run `repowise login` first.');
10
+ }
11
+ const response = await fetch(`${getApiUrl()}${path}`, {
4
12
  ...options,
5
13
  headers: {
6
14
  'Content-Type': 'application/json',
15
+ Authorization: `Bearer ${credentials.accessToken}`,
7
16
  ...options?.headers,
8
17
  },
9
18
  });
19
+ if (response.status === 401) {
20
+ await clearCredentials();
21
+ throw new Error('Session expired. Run `repowise login` again.');
22
+ }
10
23
  if (!response.ok) {
11
- const body = (await response.json());
12
- throw new Error(body.error?.message ?? 'Request failed');
24
+ let message = `Request failed with status ${response.status}`;
25
+ try {
26
+ const body = (await response.json());
27
+ if (body.error?.message)
28
+ message = body.error.message;
29
+ }
30
+ catch {
31
+ // Response body is not JSON (e.g. gateway 502/503) — use default message
32
+ }
33
+ throw new Error(message);
13
34
  }
14
35
  const json = (await response.json());
15
36
  return json.data;
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,yBAAyB,CAAC;AAM7E,MAAM,CAAC,KAAK,UAAU,UAAU,CAAI,IAAY,EAAE,OAAqB;IACrE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,GAAG,IAAI,EAAE,EAAE;QAChD,GAAG,OAAO;QACV,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,GAAG,OAAO,EAAE,OAAO;SACpB;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAa,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,gBAAgB,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAgB,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,SAAS,SAAS;IAChB,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,YAAY,EAAE,CAAC,MAAM,CAAC;AAClE,CAAC;AAMD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAI,IAAY,EAAE,OAAqB;IACrE,MAAM,WAAW,GAAG,MAAM,mBAAmB,EAAE,CAAC;IAEhD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE;QACpD,GAAG,OAAO;QACV,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,WAAW,CAAC,WAAW,EAAE;YAClD,GAAG,OAAO,EAAE,OAAO;SACpB;KACF,CAAC,CAAC;IAEH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,MAAM,gBAAgB,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,OAAO,GAAG,8BAA8B,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC9D,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAa,CAAC;YACjD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO;gBAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;QAC3E,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAgB,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC"}
@@ -1,10 +1,28 @@
1
+ export declare const CLI_CALLBACK_PORT = 19876;
1
2
  export interface StoredCredentials {
2
3
  accessToken: string;
3
4
  refreshToken: string;
4
5
  idToken: string;
5
6
  expiresAt: number;
6
7
  }
8
+ export declare function generateCodeVerifier(): string;
9
+ export declare function generateCodeChallenge(verifier: string): string;
10
+ export declare function generateState(): string;
11
+ export declare function getAuthorizeUrl(codeChallenge: string, state: string): string;
12
+ export interface CallbackResult {
13
+ code: string;
14
+ state: string;
15
+ }
16
+ export declare function startCallbackServer(): Promise<CallbackResult>;
17
+ export declare function exchangeCodeForTokens(code: string, codeVerifier: string): Promise<StoredCredentials>;
18
+ export declare function refreshTokens(refreshToken: string): Promise<StoredCredentials>;
7
19
  export declare function getStoredCredentials(): Promise<StoredCredentials | null>;
8
20
  export declare function storeCredentials(credentials: StoredCredentials): Promise<void>;
9
21
  export declare function clearCredentials(): Promise<void>;
22
+ export declare function getValidCredentials(): Promise<StoredCredentials | null>;
23
+ export declare function performLogin(): Promise<StoredCredentials>;
24
+ export declare function decodeIdToken(idToken: string): {
25
+ email: string;
26
+ tenantId?: string;
27
+ };
10
28
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/lib/auth.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAO9E;AAED,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpF;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMtD"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/lib/auth.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAmBvC,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAID,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB5E;AAQD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAAC,cAAc,CAAC,CA8E7D;AAID,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,iBAAiB,CAAC,CA+B5B;AAID,wBAAsB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA2BpF;AAID,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAY9E;AAED,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpF;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMtD;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAiB7E;AAID,wBAAsB,YAAY,IAAI,OAAO,CAAC,iBAAiB,CAAC,CA2B/D;AA0CD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAYnF"}
@@ -1,27 +1,271 @@
1
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { readFile, writeFile, mkdir, chmod, unlink } from 'node:fs/promises';
3
+ import http from 'node:http';
2
4
  import { homedir } from 'node:os';
3
5
  import { join } from 'node:path';
6
+ import { getEnvConfig } from './env.js';
4
7
  const CONFIG_DIR = join(homedir(), '.repowise');
5
8
  const CREDENTIALS_PATH = join(CONFIG_DIR, 'credentials.json');
9
+ export const CLI_CALLBACK_PORT = 19876;
10
+ const CALLBACK_TIMEOUT_MS = 120_000; // 2 minutes
11
+ // Cognito config — read lazily so tests can set env vars before function calls
12
+ function getCognitoConfig() {
13
+ const env = getEnvConfig();
14
+ return {
15
+ domain: process.env['REPOWISE_COGNITO_DOMAIN'] ?? env.cognitoDomain,
16
+ clientId: process.env['REPOWISE_COGNITO_CLIENT_ID'] ?? env.cognitoClientId,
17
+ region: process.env['REPOWISE_COGNITO_REGION'] ?? env.cognitoRegion,
18
+ customDomain: env.customDomain,
19
+ };
20
+ }
21
+ function getCognitoBaseUrl() {
22
+ const { domain, region, customDomain } = getCognitoConfig();
23
+ return customDomain ? `https://${domain}` : `https://${domain}.auth.${region}.amazoncognito.com`;
24
+ }
25
+ // --- PKCE (Proof Key for Code Exchange) ---
26
+ export function generateCodeVerifier() {
27
+ return randomBytes(32).toString('base64url');
28
+ }
29
+ export function generateCodeChallenge(verifier) {
30
+ return createHash('sha256').update(verifier).digest('base64url');
31
+ }
32
+ export function generateState() {
33
+ return randomBytes(32).toString('hex');
34
+ }
35
+ // --- Cognito URL Construction ---
36
+ export function getAuthorizeUrl(codeChallenge, state) {
37
+ const { clientId } = getCognitoConfig();
38
+ if (!clientId) {
39
+ throw new Error('Missing REPOWISE_COGNITO_CLIENT_ID environment variable. Configure it before running login.');
40
+ }
41
+ const params = new URLSearchParams({
42
+ response_type: 'code',
43
+ client_id: clientId,
44
+ redirect_uri: `http://localhost:${CLI_CALLBACK_PORT}/callback`,
45
+ code_challenge: codeChallenge,
46
+ code_challenge_method: 'S256',
47
+ scope: 'openid email profile',
48
+ state,
49
+ });
50
+ return `${getCognitoBaseUrl()}/oauth2/authorize?${params.toString()}`;
51
+ }
52
+ function getTokenUrl() {
53
+ return `${getCognitoBaseUrl()}/oauth2/token`;
54
+ }
55
+ export function startCallbackServer() {
56
+ return new Promise((resolve, reject) => {
57
+ const server = http.createServer((req, res) => {
58
+ // req.url is always defined for incoming HTTP requests (only undefined for client requests)
59
+ const url = new URL(req.url, `http://localhost:${CLI_CALLBACK_PORT}`);
60
+ if (url.pathname !== '/callback') {
61
+ res.writeHead(404);
62
+ res.end();
63
+ return;
64
+ }
65
+ const code = url.searchParams.get('code');
66
+ const state = url.searchParams.get('state');
67
+ const error = url.searchParams.get('error');
68
+ if (error) {
69
+ res.writeHead(200, { 'Content-Type': 'text/html' });
70
+ res.end(callbackPage('Authentication Failed', 'Something went wrong. Please close this tab and try again.', true));
71
+ server.close();
72
+ reject(new Error(`Authentication error: ${error}`));
73
+ return;
74
+ }
75
+ if (!code || !state) {
76
+ res.writeHead(400, { 'Content-Type': 'text/html' });
77
+ res.end(callbackPage('Missing Parameters', 'The callback was missing required data. Please close this tab and try again.', true));
78
+ server.close();
79
+ reject(new Error('Missing code or state in callback'));
80
+ return;
81
+ }
82
+ res.writeHead(200, { 'Content-Type': 'text/html' });
83
+ res.end(callbackPage('Authentication Successful', 'You can close this tab and return to the terminal.', false));
84
+ server.close();
85
+ resolve({ code, state });
86
+ });
87
+ server.listen(CLI_CALLBACK_PORT, '127.0.0.1');
88
+ server.on('error', (err) => {
89
+ if (err.code === 'EADDRINUSE') {
90
+ reject(new Error(`Port ${CLI_CALLBACK_PORT} is already in use. Close the conflicting process and try again.`));
91
+ }
92
+ else {
93
+ reject(err);
94
+ }
95
+ });
96
+ const timeout = setTimeout(() => {
97
+ server.close();
98
+ reject(new Error('Authentication timed out. Please try again.'));
99
+ }, CALLBACK_TIMEOUT_MS);
100
+ server.on('close', () => clearTimeout(timeout));
101
+ });
102
+ }
103
+ // --- Token Exchange ---
104
+ export async function exchangeCodeForTokens(code, codeVerifier) {
105
+ const response = await fetch(getTokenUrl(), {
106
+ method: 'POST',
107
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
108
+ body: new URLSearchParams({
109
+ grant_type: 'authorization_code',
110
+ client_id: getCognitoConfig().clientId,
111
+ redirect_uri: `http://localhost:${CLI_CALLBACK_PORT}/callback`,
112
+ code,
113
+ code_verifier: codeVerifier,
114
+ }),
115
+ });
116
+ if (!response.ok) {
117
+ const text = await response.text();
118
+ throw new Error(`Token exchange failed: ${response.status} ${text}`);
119
+ }
120
+ const data = (await response.json());
121
+ return {
122
+ accessToken: data.access_token,
123
+ refreshToken: data.refresh_token,
124
+ idToken: data.id_token,
125
+ expiresAt: Date.now() + data.expires_in * 1000,
126
+ };
127
+ }
128
+ // --- Token Refresh ---
129
+ export async function refreshTokens(refreshToken) {
130
+ const response = await fetch(getTokenUrl(), {
131
+ method: 'POST',
132
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
133
+ body: new URLSearchParams({
134
+ grant_type: 'refresh_token',
135
+ client_id: getCognitoConfig().clientId,
136
+ refresh_token: refreshToken,
137
+ }),
138
+ });
139
+ if (!response.ok) {
140
+ throw new Error(`Token refresh failed: ${response.status}`);
141
+ }
142
+ const data = (await response.json());
143
+ return {
144
+ accessToken: data.access_token,
145
+ refreshToken, // Cognito does not return a new refresh token
146
+ idToken: data.id_token,
147
+ expiresAt: Date.now() + data.expires_in * 1000,
148
+ };
149
+ }
150
+ // --- Credential Storage ---
6
151
  export async function getStoredCredentials() {
7
152
  try {
8
153
  const data = await readFile(CREDENTIALS_PATH, 'utf-8');
9
154
  return JSON.parse(data);
10
155
  }
11
- catch {
12
- return null;
156
+ catch (err) {
157
+ // File doesn't exist or corrupt JSON — treat as no credentials
158
+ if (err.code === 'ENOENT' || err instanceof SyntaxError) {
159
+ return null;
160
+ }
161
+ // Permission errors (EPERM, EACCES) should surface — the file exists but can't be read
162
+ throw err;
13
163
  }
14
164
  }
15
165
  export async function storeCredentials(credentials) {
16
- await mkdir(CONFIG_DIR, { recursive: true });
17
- await writeFile(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), { mode: 0o600 });
166
+ await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
167
+ await writeFile(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2));
168
+ await chmod(CREDENTIALS_PATH, 0o600);
18
169
  }
19
170
  export async function clearCredentials() {
20
171
  try {
21
- await writeFile(CREDENTIALS_PATH, '{}', { mode: 0o600 });
172
+ await unlink(CREDENTIALS_PATH);
173
+ }
174
+ catch (err) {
175
+ if (err.code !== 'ENOENT')
176
+ throw err;
177
+ }
178
+ }
179
+ export async function getValidCredentials() {
180
+ const creds = await getStoredCredentials();
181
+ if (!creds)
182
+ return null;
183
+ // Refresh if within 5 minutes of expiry
184
+ if (Date.now() > creds.expiresAt - 5 * 60 * 1000) {
185
+ try {
186
+ const refreshed = await refreshTokens(creds.refreshToken);
187
+ await storeCredentials(refreshed);
188
+ return refreshed;
189
+ }
190
+ catch {
191
+ await clearCredentials();
192
+ return null;
193
+ }
194
+ }
195
+ return creds;
196
+ }
197
+ // --- Auto-Login (Story 2.1) ---
198
+ export async function performLogin() {
199
+ const codeVerifier = generateCodeVerifier();
200
+ const codeChallenge = generateCodeChallenge(codeVerifier);
201
+ const state = generateState();
202
+ const authorizeUrl = getAuthorizeUrl(codeChallenge, state);
203
+ const callbackPromise = startCallbackServer();
204
+ try {
205
+ const open = (await import('open')).default;
206
+ await open(authorizeUrl);
207
+ }
208
+ catch {
209
+ // Print URL as fallback
210
+ console.log(`\nOpen this URL in your browser to authenticate:\n`);
211
+ console.log(authorizeUrl);
212
+ }
213
+ const { code, state: returnedState } = await callbackPromise;
214
+ if (returnedState !== state) {
215
+ throw new Error('State mismatch — possible CSRF attack. Please try again.');
216
+ }
217
+ const credentials = await exchangeCodeForTokens(code, codeVerifier);
218
+ await storeCredentials(credentials);
219
+ return credentials;
220
+ }
221
+ // --- ID Token Decoding ---
222
+ function callbackPage(title, message, isError) {
223
+ const icon = isError
224
+ ? '<svg width="48" height="48" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="#ef4444" stroke-width="2"/><path stroke="#ef4444" stroke-width="2" stroke-linecap="round" d="M15 9l-6 6M9 9l6 6"/></svg>'
225
+ : '<svg width="48" height="48" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="#10b981" stroke-width="2"/><path stroke="#10b981" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M8 12l3 3 5-5"/></svg>';
226
+ return `<!DOCTYPE html>
227
+ <html lang="en">
228
+ <head>
229
+ <meta charset="UTF-8">
230
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
231
+ <title>${title} — RepoWise</title>
232
+ <link rel="icon" href="https://staging.repowise.ai/favicon.svg" type="image/svg+xml">
233
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
234
+ <style>
235
+ * { margin: 0; padding: 0; box-sizing: border-box; }
236
+ body { font-family: 'Inter', system-ui, sans-serif; background: #0a0b14; color: #e4e4e7; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
237
+ .card { text-align: center; max-width: 440px; padding: 48px 40px; }
238
+ .logo { margin-bottom: 32px; }
239
+ .logo svg { height: 48px; width: auto; }
240
+ .icon { margin-bottom: 20px; }
241
+ h1 { font-size: 24px; font-weight: 700; margin-bottom: 8px; color: ${isError ? '#ef4444' : '#e4e4e7'}; }
242
+ p { font-size: 15px; color: #a1a1aa; line-height: 1.5; }
243
+ </style>
244
+ </head>
245
+ <body>
246
+ <div class="card">
247
+ <div class="logo">
248
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 50" height="48">
249
+ <text x="0" y="38" font-family="Inter, system-ui, sans-serif" font-weight="700" font-size="36" fill="#e4e4e7">Repo<tspan fill="#6c5ce7">Wise</tspan></text>
250
+ </svg>
251
+ </div>
252
+ <div class="icon">${icon}</div>
253
+ <h1>${title}</h1>
254
+ <p>${message}</p>
255
+ </div>
256
+ </body>
257
+ </html>`;
258
+ }
259
+ export function decodeIdToken(idToken) {
260
+ try {
261
+ const parts = idToken.split('.');
262
+ if (parts.length < 2)
263
+ return { email: 'unknown' };
264
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
265
+ return { email: payload.email ?? 'unknown', tenantId: payload['custom:tenant_id'] };
22
266
  }
23
267
  catch {
24
- // Ignore if file doesn't exist
268
+ return { email: 'unknown' };
25
269
  }
26
270
  }
27
271
  //# sourceMappingURL=auth.js.map