signet-auth 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of signet-auth might be problematic. Click here for more details.

Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +393 -0
  3. package/bin/sig.js +65 -0
  4. package/dist/auth-manager.d.ts +90 -0
  5. package/dist/auth-manager.js +262 -0
  6. package/dist/browser/adapters/playwright.adapter.d.ts +14 -0
  7. package/dist/browser/adapters/playwright.adapter.js +188 -0
  8. package/dist/browser/flows/form-login.flow.d.ts +6 -0
  9. package/dist/browser/flows/form-login.flow.js +35 -0
  10. package/dist/browser/flows/header-capture.d.ts +23 -0
  11. package/dist/browser/flows/header-capture.js +104 -0
  12. package/dist/browser/flows/hybrid-flow.d.ts +37 -0
  13. package/dist/browser/flows/hybrid-flow.js +104 -0
  14. package/dist/browser/flows/oauth-consent.flow.d.ts +20 -0
  15. package/dist/browser/flows/oauth-consent.flow.js +170 -0
  16. package/dist/cli/commands/doctor.d.ts +6 -0
  17. package/dist/cli/commands/doctor.js +263 -0
  18. package/dist/cli/commands/get.d.ts +2 -0
  19. package/dist/cli/commands/get.js +83 -0
  20. package/dist/cli/commands/init.d.ts +6 -0
  21. package/dist/cli/commands/init.js +244 -0
  22. package/dist/cli/commands/login.d.ts +2 -0
  23. package/dist/cli/commands/login.js +77 -0
  24. package/dist/cli/commands/logout.d.ts +2 -0
  25. package/dist/cli/commands/logout.js +11 -0
  26. package/dist/cli/commands/providers.d.ts +2 -0
  27. package/dist/cli/commands/providers.js +30 -0
  28. package/dist/cli/commands/remote.d.ts +1 -0
  29. package/dist/cli/commands/remote.js +67 -0
  30. package/dist/cli/commands/request.d.ts +2 -0
  31. package/dist/cli/commands/request.js +82 -0
  32. package/dist/cli/commands/status.d.ts +2 -0
  33. package/dist/cli/commands/status.js +41 -0
  34. package/dist/cli/commands/sync.d.ts +2 -0
  35. package/dist/cli/commands/sync.js +62 -0
  36. package/dist/cli/formatters.d.ts +3 -0
  37. package/dist/cli/formatters.js +25 -0
  38. package/dist/cli/main.d.ts +8 -0
  39. package/dist/cli/main.js +125 -0
  40. package/dist/config/generator.d.ts +24 -0
  41. package/dist/config/generator.js +97 -0
  42. package/dist/config/loader.d.ts +21 -0
  43. package/dist/config/loader.js +54 -0
  44. package/dist/config/schema.d.ts +44 -0
  45. package/dist/config/schema.js +8 -0
  46. package/dist/config/validator.d.ts +15 -0
  47. package/dist/config/validator.js +228 -0
  48. package/dist/core/errors.d.ts +57 -0
  49. package/dist/core/errors.js +107 -0
  50. package/dist/core/interfaces/auth-strategy.d.ts +48 -0
  51. package/dist/core/interfaces/auth-strategy.js +1 -0
  52. package/dist/core/interfaces/browser-adapter.d.ts +73 -0
  53. package/dist/core/interfaces/browser-adapter.js +1 -0
  54. package/dist/core/interfaces/provider.d.ts +15 -0
  55. package/dist/core/interfaces/provider.js +1 -0
  56. package/dist/core/interfaces/storage.d.ts +21 -0
  57. package/dist/core/interfaces/storage.js +1 -0
  58. package/dist/core/result.d.ts +21 -0
  59. package/dist/core/result.js +16 -0
  60. package/dist/core/types.d.ts +128 -0
  61. package/dist/core/types.js +6 -0
  62. package/dist/deps.d.ts +20 -0
  63. package/dist/deps.js +54 -0
  64. package/dist/index.d.ts +35 -0
  65. package/dist/index.js +37 -0
  66. package/dist/providers/auto-provision.d.ts +9 -0
  67. package/dist/providers/auto-provision.js +27 -0
  68. package/dist/providers/config-loader.d.ts +7 -0
  69. package/dist/providers/config-loader.js +7 -0
  70. package/dist/providers/provider-registry.d.ts +19 -0
  71. package/dist/providers/provider-registry.js +68 -0
  72. package/dist/storage/cached-storage.d.ts +24 -0
  73. package/dist/storage/cached-storage.js +57 -0
  74. package/dist/storage/directory-storage.d.ts +25 -0
  75. package/dist/storage/directory-storage.js +184 -0
  76. package/dist/storage/memory-storage.d.ts +14 -0
  77. package/dist/storage/memory-storage.js +27 -0
  78. package/dist/strategies/api-token.strategy.d.ts +6 -0
  79. package/dist/strategies/api-token.strategy.js +63 -0
  80. package/dist/strategies/basic-auth.strategy.d.ts +6 -0
  81. package/dist/strategies/basic-auth.strategy.js +41 -0
  82. package/dist/strategies/cookie.strategy.d.ts +6 -0
  83. package/dist/strategies/cookie.strategy.js +118 -0
  84. package/dist/strategies/oauth2.strategy.d.ts +6 -0
  85. package/dist/strategies/oauth2.strategy.js +134 -0
  86. package/dist/strategies/registry.d.ts +13 -0
  87. package/dist/strategies/registry.js +25 -0
  88. package/dist/sync/remote-config.d.ts +8 -0
  89. package/dist/sync/remote-config.js +49 -0
  90. package/dist/sync/sync-engine.d.ts +10 -0
  91. package/dist/sync/sync-engine.js +96 -0
  92. package/dist/sync/transports/ssh.d.ts +18 -0
  93. package/dist/sync/transports/ssh.js +115 -0
  94. package/dist/sync/types.d.ts +17 -0
  95. package/dist/sync/types.js +1 -0
  96. package/dist/utils/duration.d.ts +9 -0
  97. package/dist/utils/duration.js +34 -0
  98. package/dist/utils/http.d.ts +4 -0
  99. package/dist/utils/http.js +10 -0
  100. package/dist/utils/jwt.d.ts +15 -0
  101. package/dist/utils/jwt.js +30 -0
  102. package/package.json +56 -0
  103. package/src/auth-manager.ts +331 -0
  104. package/src/browser/adapters/playwright.adapter.ts +247 -0
  105. package/src/browser/flows/form-login.flow.ts +35 -0
  106. package/src/browser/flows/header-capture.ts +128 -0
  107. package/src/browser/flows/hybrid-flow.ts +165 -0
  108. package/src/browser/flows/oauth-consent.flow.ts +200 -0
  109. package/src/cli/commands/doctor.ts +301 -0
  110. package/src/cli/commands/get.ts +96 -0
  111. package/src/cli/commands/init.ts +289 -0
  112. package/src/cli/commands/login.ts +94 -0
  113. package/src/cli/commands/logout.ts +17 -0
  114. package/src/cli/commands/providers.ts +39 -0
  115. package/src/cli/commands/remote.ts +71 -0
  116. package/src/cli/commands/request.ts +97 -0
  117. package/src/cli/commands/status.ts +48 -0
  118. package/src/cli/commands/sync.ts +71 -0
  119. package/src/cli/formatters.ts +31 -0
  120. package/src/cli/main.ts +144 -0
  121. package/src/config/generator.ts +122 -0
  122. package/src/config/loader.ts +70 -0
  123. package/src/config/schema.ts +75 -0
  124. package/src/config/validator.ts +281 -0
  125. package/src/core/errors.ts +182 -0
  126. package/src/core/interfaces/auth-strategy.ts +65 -0
  127. package/src/core/interfaces/browser-adapter.ts +81 -0
  128. package/src/core/interfaces/provider.ts +19 -0
  129. package/src/core/interfaces/storage.ts +26 -0
  130. package/src/core/result.ts +24 -0
  131. package/src/core/types.ts +194 -0
  132. package/src/deps.ts +80 -0
  133. package/src/index.ts +109 -0
  134. package/src/providers/auto-provision.ts +30 -0
  135. package/src/providers/config-loader.ts +8 -0
  136. package/src/providers/provider-registry.ts +79 -0
  137. package/src/storage/cached-storage.ts +72 -0
  138. package/src/storage/directory-storage.ts +204 -0
  139. package/src/storage/memory-storage.ts +35 -0
  140. package/src/strategies/api-token.strategy.ts +87 -0
  141. package/src/strategies/basic-auth.strategy.ts +64 -0
  142. package/src/strategies/cookie.strategy.ts +153 -0
  143. package/src/strategies/oauth2.strategy.ts +178 -0
  144. package/src/strategies/registry.ts +34 -0
  145. package/src/sync/remote-config.ts +60 -0
  146. package/src/sync/sync-engine.ts +113 -0
  147. package/src/sync/transports/ssh.ts +130 -0
  148. package/src/sync/types.ts +15 -0
  149. package/src/utils/duration.ts +34 -0
  150. package/src/utils/http.ts +11 -0
  151. package/src/utils/jwt.ts +39 -0
  152. package/tsconfig.json +20 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * sig init — Interactive setup command.
3
+ * Creates ~/.signet/config.yaml with sensible defaults.
4
+ * Does NOT take deps (runs before config exists).
5
+ */
6
+ import fs from 'node:fs';
7
+ import fsp from 'node:fs/promises';
8
+ import path from 'node:path';
9
+ import os from 'node:os';
10
+ import { execSync } from 'node:child_process';
11
+ import { createInterface } from 'node:readline/promises';
12
+ import YAML from 'yaml';
13
+ import { getConfigPath } from '../../config/loader.js';
14
+ import { generateConfigYaml } from '../../config/generator.js';
15
+ import { validateConfig } from '../../config/validator.js';
16
+ import { isOk } from '../../core/result.js';
17
+ const PROVIDER_TEMPLATES = {
18
+ github: {
19
+ name: 'GitHub',
20
+ domains: ['github.com', 'api.github.com'],
21
+ strategy: 'api-token',
22
+ config: { headerName: 'Authorization', headerPrefix: 'Bearer' },
23
+ },
24
+ gitlab: {
25
+ name: 'GitLab',
26
+ domains: ['gitlab.com'],
27
+ strategy: 'api-token',
28
+ config: { headerName: 'PRIVATE-TOKEN', headerPrefix: '' },
29
+ },
30
+ jira: {
31
+ name: 'Jira (Cloud)',
32
+ domains: [],
33
+ strategy: 'cookie',
34
+ needsDomain: true,
35
+ },
36
+ confluence: {
37
+ name: 'Confluence',
38
+ domains: [],
39
+ strategy: 'cookie',
40
+ needsDomain: true,
41
+ },
42
+ };
43
+ // ---------------------------------------------------------------------------
44
+ // Browser channel detection
45
+ // ---------------------------------------------------------------------------
46
+ function detectBrowserChannel() {
47
+ const platform = process.platform;
48
+ if (platform === 'darwin') {
49
+ if (fs.existsSync('/Applications/Google Chrome.app'))
50
+ return 'chrome';
51
+ if (fs.existsSync('/Applications/Microsoft Edge.app'))
52
+ return 'msedge';
53
+ return 'chrome';
54
+ }
55
+ if (platform === 'linux') {
56
+ try {
57
+ execSync('which google-chrome', { stdio: 'ignore' });
58
+ return 'chrome';
59
+ }
60
+ catch { /* not found */ }
61
+ try {
62
+ execSync('which microsoft-edge', { stdio: 'ignore' });
63
+ return 'msedge';
64
+ }
65
+ catch { /* not found */ }
66
+ try {
67
+ execSync('which chromium', { stdio: 'ignore' });
68
+ return 'chromium';
69
+ }
70
+ catch { /* not found */ }
71
+ return 'chrome';
72
+ }
73
+ // Windows or unknown
74
+ return 'chrome';
75
+ }
76
+ // ---------------------------------------------------------------------------
77
+ // Interactive prompts
78
+ // ---------------------------------------------------------------------------
79
+ async function promptProviders(rl) {
80
+ const providers = [];
81
+ const addMore = await rl.question('\nWould you like to add a provider? (y/N) ');
82
+ if (addMore.toLowerCase() !== 'y')
83
+ return providers;
84
+ let keepAdding = true;
85
+ while (keepAdding) {
86
+ const templateNames = Object.keys(PROVIDER_TEMPLATES);
87
+ console.log('\nProvider templates:');
88
+ for (let i = 0; i < templateNames.length; i++) {
89
+ const key = templateNames[i];
90
+ const tmpl = PROVIDER_TEMPLATES[key];
91
+ console.log(` ${i + 1}. ${tmpl.name} (${key})`);
92
+ }
93
+ console.log(` ${templateNames.length + 1}. Custom`);
94
+ const choice = await rl.question(`\nSelect template (1-${templateNames.length + 1}): `);
95
+ const choiceNum = parseInt(choice, 10);
96
+ if (choiceNum >= 1 && choiceNum <= templateNames.length) {
97
+ const templateKey = templateNames[choiceNum - 1];
98
+ const template = PROVIDER_TEMPLATES[templateKey];
99
+ let domains = template.domains;
100
+ if (template.needsDomain) {
101
+ const domain = await rl.question(`Enter your ${template.name} domain (e.g., ${templateKey}.example.com): `);
102
+ if (domain.trim()) {
103
+ domains = [domain.trim()];
104
+ }
105
+ else {
106
+ console.log(' Skipping — domain is required.');
107
+ const again = await rl.question('\nAdd another provider? (y/N) ');
108
+ keepAdding = again.toLowerCase() === 'y';
109
+ continue;
110
+ }
111
+ }
112
+ providers.push({
113
+ id: templateKey,
114
+ domains,
115
+ strategy: template.strategy,
116
+ ...(template.config ? { config: template.config } : {}),
117
+ });
118
+ console.log(` Added ${template.name}.`);
119
+ }
120
+ else if (choiceNum === templateNames.length + 1) {
121
+ const id = await rl.question('Provider id (e.g., my-api): ');
122
+ if (!id.trim()) {
123
+ console.log(' Skipping — id is required.');
124
+ const again = await rl.question('\nAdd another provider? (y/N) ');
125
+ keepAdding = again.toLowerCase() === 'y';
126
+ continue;
127
+ }
128
+ const domain = await rl.question('Domain(s) (comma-separated): ');
129
+ const domains = domain.split(',').map(d => d.trim()).filter(Boolean);
130
+ if (domains.length === 0) {
131
+ console.log(' Skipping — at least one domain is required.');
132
+ const again = await rl.question('\nAdd another provider? (y/N) ');
133
+ keepAdding = again.toLowerCase() === 'y';
134
+ continue;
135
+ }
136
+ const strategy = await rl.question('Strategy (cookie, oauth2, api-token, basic) [cookie]: ') || 'cookie';
137
+ providers.push({ id: id.trim(), domains, strategy });
138
+ console.log(` Added custom provider "${id.trim()}".`);
139
+ }
140
+ else {
141
+ console.log(' Invalid selection.');
142
+ }
143
+ const again = await rl.question('\nAdd another provider? (y/N) ');
144
+ keepAdding = again.toLowerCase() === 'y';
145
+ }
146
+ return providers;
147
+ }
148
+ // ---------------------------------------------------------------------------
149
+ // Main command
150
+ // ---------------------------------------------------------------------------
151
+ export async function runInit(positionals, flags) {
152
+ const configPath = getConfigPath();
153
+ const signetDir = path.dirname(configPath);
154
+ const force = flags.force === true;
155
+ const yes = flags.yes === true;
156
+ // Check if config already exists
157
+ if (fs.existsSync(configPath) && !force) {
158
+ process.stderr.write(`Config file already exists: ${configPath}\n` +
159
+ 'Use --force to overwrite.\n');
160
+ process.exitCode = 1;
161
+ return;
162
+ }
163
+ // Detect defaults
164
+ const detectedChannel = detectBrowserChannel();
165
+ const defaultChannel = typeof flags.channel === 'string' ? flags.channel : detectedChannel;
166
+ const defaultBrowserDataDir = typeof flags['browser-data-dir'] === 'string'
167
+ ? flags['browser-data-dir']
168
+ : path.join(signetDir, 'browser-data');
169
+ const defaultCredentialsDir = typeof flags['credentials-dir'] === 'string'
170
+ ? flags['credentials-dir']
171
+ : path.join(signetDir, 'credentials');
172
+ let channel = defaultChannel;
173
+ let browserDataDir = defaultBrowserDataDir;
174
+ let credentialsDir = defaultCredentialsDir;
175
+ let providers = [];
176
+ // Interactive mode
177
+ const isTTY = process.stdin.isTTY && process.stdout.isTTY;
178
+ if (isTTY && !yes) {
179
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
180
+ try {
181
+ console.log('\nWelcome to Signet! Let\'s set up your configuration.\n');
182
+ const channelAnswer = await rl.question(`Browser channel [${defaultChannel}]: `);
183
+ if (channelAnswer.trim())
184
+ channel = channelAnswer.trim();
185
+ providers = await promptProviders(rl);
186
+ }
187
+ finally {
188
+ rl.close();
189
+ }
190
+ }
191
+ else {
192
+ if (!yes) {
193
+ // Non-TTY, non-yes: use defaults silently
194
+ }
195
+ }
196
+ // Resolve ~ in paths for display but keep ~ in config for portability
197
+ const displayBrowserDataDir = browserDataDir.replace(os.homedir(), '~');
198
+ const displayCredentialsDir = credentialsDir.replace(os.homedir(), '~');
199
+ // Generate config YAML
200
+ const yaml = generateConfigYaml({
201
+ channel,
202
+ browserDataDir: displayBrowserDataDir,
203
+ credentialsDir: displayCredentialsDir,
204
+ headlessTimeout: 30_000,
205
+ visibleTimeout: 120_000,
206
+ waitUntil: 'load',
207
+ providers: providers.length > 0 ? providers : undefined,
208
+ });
209
+ // Validate the generated config before writing (sanity check)
210
+ let raw;
211
+ try {
212
+ raw = YAML.parse(yaml);
213
+ }
214
+ catch (e) {
215
+ process.stderr.write(`Bug: generated invalid YAML: ${e.message}\n`);
216
+ process.exitCode = 1;
217
+ return;
218
+ }
219
+ const validationResult = validateConfig(raw);
220
+ if (!isOk(validationResult)) {
221
+ process.stderr.write(`Bug: generated config failed validation: ${validationResult.error.message}\n`);
222
+ process.exitCode = 1;
223
+ return;
224
+ }
225
+ // Create directories
226
+ await fsp.mkdir(signetDir, { recursive: true });
227
+ await fsp.mkdir(browserDataDir, { recursive: true });
228
+ await fsp.mkdir(credentialsDir, { recursive: true });
229
+ // Write config
230
+ await fsp.writeFile(configPath, yaml, 'utf-8');
231
+ // Success message
232
+ console.log(`\n Config written to ${configPath}`);
233
+ console.log(` Browser data: ${browserDataDir}`);
234
+ console.log(` Credentials: ${credentialsDir}`);
235
+ console.log(` Browser: ${channel}`);
236
+ if (providers.length > 0) {
237
+ console.log(` Providers: ${providers.map(p => p.id).join(', ')}`);
238
+ }
239
+ console.log('\nNext steps:');
240
+ console.log(' sig login <url> Authenticate with a service');
241
+ console.log(' sig providers List configured providers');
242
+ console.log(' sig doctor Check your setup');
243
+ console.log('');
244
+ }
@@ -0,0 +1,2 @@
1
+ import type { AuthDeps } from '../../deps.js';
2
+ export declare function runLogin(positionals: string[], flags: Record<string, string | boolean>, deps: AuthDeps): Promise<void>;
@@ -0,0 +1,77 @@
1
+ import { buildStrategyConfig } from '../../config/validator.js';
2
+ import { isOk } from '../../core/result.js';
3
+ import { formatJson } from '../formatters.js';
4
+ export async function runLogin(positionals, flags, deps) {
5
+ const url = positionals[0];
6
+ if (!url) {
7
+ process.stderr.write('Usage: sig login <url>\n');
8
+ process.exitCode = 1;
9
+ return;
10
+ }
11
+ const baseProvider = deps.authManager.resolveProvider(url);
12
+ const hasOverrides = flags.strategy !== undefined;
13
+ const provider = hasOverrides
14
+ ? { ...baseProvider }
15
+ : baseProvider;
16
+ if (typeof flags.strategy === 'string') {
17
+ const strategyName = flags.strategy;
18
+ provider.strategy = strategyName;
19
+ provider.strategyConfig = buildStrategyConfig(strategyName);
20
+ }
21
+ if (hasOverrides) {
22
+ deps.authManager.providerRegistry.register(provider);
23
+ }
24
+ if (typeof flags.token === 'string') {
25
+ // Read headerName/headerPrefix from the typed strategy config if api-token
26
+ const headerName = provider.strategyConfig.strategy === 'api-token'
27
+ ? provider.strategyConfig.headerName ?? 'Authorization'
28
+ : 'Authorization';
29
+ const headerPrefix = provider.strategyConfig.strategy === 'api-token'
30
+ ? provider.strategyConfig.headerPrefix ?? 'Bearer'
31
+ : 'Bearer';
32
+ const credential = {
33
+ type: 'api-key',
34
+ key: flags.token,
35
+ headerName,
36
+ headerPrefix,
37
+ };
38
+ const result = await deps.authManager.setCredential(provider.id, credential);
39
+ if (!isOk(result)) {
40
+ process.stderr.write(`Error: ${result.error.message}\n`);
41
+ process.exitCode = 1;
42
+ return;
43
+ }
44
+ process.stderr.write(`Token stored for "${provider.name}" (${provider.id}).\n`);
45
+ process.stdout.write(formatJson({ provider: provider.id, type: 'api-key' }) + '\n');
46
+ return;
47
+ }
48
+ if (typeof flags.username === 'string' && typeof flags.password === 'string') {
49
+ const credential = {
50
+ type: 'basic',
51
+ username: flags.username,
52
+ password: flags.password,
53
+ };
54
+ const result = await deps.authManager.setCredential(provider.id, credential);
55
+ if (!isOk(result)) {
56
+ process.stderr.write(`Error: ${result.error.message}\n`);
57
+ process.exitCode = 1;
58
+ return;
59
+ }
60
+ process.stderr.write(`Basic auth credentials stored for "${provider.name}" (${provider.id}).\n`);
61
+ process.stdout.write(formatJson({ provider: provider.id, type: 'basic' }) + '\n');
62
+ return;
63
+ }
64
+ process.stderr.write(`Authenticating with "${provider.name}" via browser...\n`);
65
+ const result = await deps.authManager.forceReauth(provider.id);
66
+ if (!isOk(result)) {
67
+ process.stderr.write(`Authentication failed: ${result.error.message}\n`);
68
+ process.exit(1);
69
+ }
70
+ const status = await deps.authManager.getStatus(provider.id);
71
+ process.stderr.write(`Authenticated with "${provider.name}".\n`);
72
+ process.stdout.write(formatJson({
73
+ provider: provider.id,
74
+ type: result.value.type,
75
+ ...(status.expiresAt ? { expiresAt: status.expiresAt } : {}),
76
+ }) + '\n');
77
+ }
@@ -0,0 +1,2 @@
1
+ import type { AuthDeps } from '../../deps.js';
2
+ export declare function runLogout(positionals: string[], flags: Record<string, string | boolean>, deps: AuthDeps): Promise<void>;
@@ -0,0 +1,11 @@
1
+ export async function runLogout(positionals, flags, deps) {
2
+ const providerId = positionals[0];
3
+ if (providerId) {
4
+ await deps.authManager.clearCredentials(providerId);
5
+ process.stderr.write(`Credentials cleared for "${providerId}".\n`);
6
+ }
7
+ else {
8
+ await deps.authManager.clearAll();
9
+ process.stderr.write('All credentials cleared.\n');
10
+ }
11
+ }
@@ -0,0 +1,2 @@
1
+ import type { AuthDeps } from '../../deps.js';
2
+ export declare function runProviders(positionals: string[], flags: Record<string, string | boolean>, deps: AuthDeps): Promise<void>;
@@ -0,0 +1,30 @@
1
+ import { formatJson, formatTable } from '../formatters.js';
2
+ export async function runProviders(positionals, flags, deps) {
3
+ const format = flags.format ?? (process.stdout.isTTY ? 'table' : 'json');
4
+ const providers = deps.authManager.providerRegistry.list();
5
+ const statuses = await Promise.all(providers.map(p => deps.authManager.getStatus(p.id)));
6
+ if (format === 'json') {
7
+ const output = statuses.map(s => ({
8
+ id: s.id,
9
+ name: s.name,
10
+ strategy: s.strategy,
11
+ configured: s.configured,
12
+ valid: s.valid,
13
+ credentialType: s.credentialType ?? null,
14
+ }));
15
+ process.stdout.write(formatJson(output) + '\n');
16
+ }
17
+ else {
18
+ if (statuses.length === 0) {
19
+ process.stderr.write('No providers configured.\n');
20
+ return;
21
+ }
22
+ const rows = statuses.map(s => ({
23
+ id: s.id,
24
+ name: s.name,
25
+ strategy: s.strategy,
26
+ status: s.valid ? 'authenticated' : 'not authenticated',
27
+ }));
28
+ process.stdout.write(formatTable(rows) + '\n');
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ export declare function runRemote(positionals: string[], flags: Record<string, string | boolean>): Promise<void>;
@@ -0,0 +1,67 @@
1
+ import { getRemotes, addRemote, removeRemote } from '../../sync/remote-config.js';
2
+ import { formatJson, formatTable } from '../formatters.js';
3
+ export async function runRemote(positionals, flags) {
4
+ const subcommand = positionals[0];
5
+ switch (subcommand) {
6
+ case 'add': {
7
+ const name = positionals[1];
8
+ const host = positionals[2];
9
+ if (!name || !host) {
10
+ process.stderr.write('Usage: sig remote add <name> <host> [--user <user>] [--path <path>] [--ssh-key <key>]\n');
11
+ process.exitCode = 1;
12
+ return;
13
+ }
14
+ const remote = {
15
+ name,
16
+ type: 'ssh',
17
+ host,
18
+ ...(typeof flags.user === 'string' ? { user: flags.user } : {}),
19
+ ...(typeof flags.path === 'string' ? { path: flags.path } : {}),
20
+ ...(typeof flags['ssh-key'] === 'string' ? { sshKey: flags['ssh-key'] } : {}),
21
+ };
22
+ await addRemote(remote);
23
+ process.stderr.write(`Remote "${name}" added (${host})\n`);
24
+ return;
25
+ }
26
+ case 'remove': {
27
+ const name = positionals[1];
28
+ if (!name) {
29
+ process.stderr.write('Usage: sig remote remove <name>\n');
30
+ process.exitCode = 1;
31
+ return;
32
+ }
33
+ const removed = await removeRemote(name);
34
+ if (removed) {
35
+ process.stderr.write(`Remote "${name}" removed\n`);
36
+ }
37
+ else {
38
+ process.stderr.write(`Remote "${name}" not found\n`);
39
+ process.exitCode = 1;
40
+ }
41
+ return;
42
+ }
43
+ case 'list':
44
+ default: {
45
+ const remotes = await getRemotes();
46
+ if (remotes.length === 0) {
47
+ process.stderr.write('No remotes configured. Use "sig remote add <name> <host>" to add one.\n');
48
+ return;
49
+ }
50
+ const format = typeof flags.format === 'string' ? flags.format : 'table';
51
+ if (format === 'json') {
52
+ process.stdout.write(formatJson(remotes) + '\n');
53
+ }
54
+ else {
55
+ const rows = remotes.map(r => ({
56
+ name: r.name,
57
+ type: r.type,
58
+ host: r.host,
59
+ user: r.user ?? '-',
60
+ path: r.path ?? '~/.signet/credentials',
61
+ }));
62
+ process.stdout.write(formatTable(rows) + '\n');
63
+ }
64
+ return;
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,2 @@
1
+ import type { AuthDeps } from '../../deps.js';
2
+ export declare function runRequest(positionals: string[], flags: Record<string, string | boolean>, deps: AuthDeps): Promise<void>;
@@ -0,0 +1,82 @@
1
+ import { isOk } from '../../core/result.js';
2
+ import { buildUserAgent } from '../../utils/http.js';
3
+ import { formatJson } from '../formatters.js';
4
+ export async function runRequest(positionals, flags, deps) {
5
+ const url = positionals[0];
6
+ if (!url) {
7
+ process.stderr.write('Usage: sig request <url> [--method GET] [--header "Name: Value"] [--body \'{}\']\n');
8
+ process.exitCode = 1;
9
+ return;
10
+ }
11
+ const result = await deps.authManager.getCredentialsByUrl(url);
12
+ if (!isOk(result)) {
13
+ process.stderr.write(`Auth error: ${result.error.message}\n`);
14
+ process.exitCode = 1;
15
+ return;
16
+ }
17
+ const { provider, credential } = result.value;
18
+ const authHeaders = deps.authManager.applyToRequest(provider.id, credential);
19
+ const requestHeaders = {
20
+ 'User-Agent': buildUserAgent(),
21
+ ...authHeaders,
22
+ };
23
+ // Parse --header flags (may appear multiple times via positionals workaround, or as single value)
24
+ if (typeof flags.header === 'string') {
25
+ const idx = flags.header.indexOf(':');
26
+ if (idx > 0) {
27
+ requestHeaders[flags.header.slice(0, idx).trim()] = flags.header.slice(idx + 1).trim();
28
+ }
29
+ }
30
+ const httpMethod = (flags.method ?? 'GET').toUpperCase();
31
+ const fetchOptions = { method: httpMethod, headers: requestHeaders };
32
+ const body = flags.body;
33
+ if (body && ['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
34
+ fetchOptions.body = body;
35
+ if (!requestHeaders['Content-Type']) {
36
+ requestHeaders['Content-Type'] = 'application/json';
37
+ }
38
+ }
39
+ try {
40
+ const response = await fetch(url, fetchOptions);
41
+ const responseBody = await response.text();
42
+ let formattedBody;
43
+ try {
44
+ formattedBody = JSON.stringify(JSON.parse(responseBody), null, 2);
45
+ }
46
+ catch {
47
+ formattedBody = responseBody;
48
+ }
49
+ const format = flags.format ?? 'json';
50
+ switch (format) {
51
+ case 'body':
52
+ process.stdout.write(formattedBody + '\n');
53
+ break;
54
+ case 'headers': {
55
+ process.stdout.write(`${response.status} ${response.statusText}\n`);
56
+ response.headers.forEach((value, key) => {
57
+ process.stdout.write(`${key}: ${value}\n`);
58
+ });
59
+ break;
60
+ }
61
+ case 'json':
62
+ default: {
63
+ const responseHeaders = {};
64
+ response.headers.forEach((value, key) => { responseHeaders[key] = value; });
65
+ process.stdout.write(formatJson({
66
+ status: response.status,
67
+ statusText: response.statusText,
68
+ headers: responseHeaders,
69
+ body: formattedBody,
70
+ }) + '\n');
71
+ break;
72
+ }
73
+ }
74
+ if (!response.ok) {
75
+ process.exitCode = 1;
76
+ }
77
+ }
78
+ catch (e) {
79
+ process.stderr.write(`Request failed: ${e.message}\n`);
80
+ process.exitCode = 1;
81
+ }
82
+ }
@@ -0,0 +1,2 @@
1
+ import type { AuthDeps } from '../../deps.js';
2
+ export declare function runStatus(positionals: string[], flags: Record<string, string | boolean>, deps: AuthDeps): Promise<void>;
@@ -0,0 +1,41 @@
1
+ import { formatJson, formatTable } from '../formatters.js';
2
+ export async function runStatus(positionals, flags, deps) {
3
+ const providerId = flags.provider ?? positionals[0];
4
+ const format = flags.format ?? (process.stdout.isTTY ? 'table' : 'json');
5
+ if (providerId) {
6
+ const status = await deps.authManager.getStatus(providerId);
7
+ if (format === 'json') {
8
+ process.stdout.write(formatJson(status) + '\n');
9
+ }
10
+ else {
11
+ process.stdout.write(formatTable([{
12
+ id: status.id,
13
+ name: status.name,
14
+ strategy: status.strategy,
15
+ valid: status.valid ? 'yes' : 'no',
16
+ type: status.credentialType ?? '-',
17
+ expires: status.expiresInMinutes !== undefined ? `${status.expiresInMinutes}m` : '-',
18
+ }]) + '\n');
19
+ }
20
+ return;
21
+ }
22
+ const statuses = await deps.authManager.getAllStatus();
23
+ if (format === 'json') {
24
+ process.stdout.write(formatJson(statuses) + '\n');
25
+ }
26
+ else {
27
+ if (statuses.length === 0) {
28
+ process.stderr.write('No providers configured.\n');
29
+ return;
30
+ }
31
+ const rows = statuses.map(s => ({
32
+ id: s.id,
33
+ name: s.name,
34
+ strategy: s.strategy,
35
+ valid: s.valid ? 'yes' : 'no',
36
+ type: s.credentialType ?? '-',
37
+ expires: s.expiresInMinutes !== undefined ? `${s.expiresInMinutes}m` : '-',
38
+ }));
39
+ process.stdout.write(formatTable(rows) + '\n');
40
+ }
41
+ }
@@ -0,0 +1,2 @@
1
+ import type { AuthDeps } from '../../deps.js';
2
+ export declare function runSync(positionals: string[], flags: Record<string, string | boolean>, deps: AuthDeps): Promise<void>;
@@ -0,0 +1,62 @@
1
+ import { getRemote, getRemotes } from '../../sync/remote-config.js';
2
+ import { SyncEngine } from '../../sync/sync-engine.js';
3
+ import { formatJson } from '../formatters.js';
4
+ export async function runSync(positionals, flags, deps) {
5
+ const subcommand = positionals[0];
6
+ if (subcommand !== 'push' && subcommand !== 'pull') {
7
+ process.stderr.write('Usage: sig sync <push|pull> [remote] [--provider <id>] [--force]\n');
8
+ process.exitCode = 1;
9
+ return;
10
+ }
11
+ // Resolve remote: explicit name or default (if only one configured)
12
+ const remoteName = positionals[1];
13
+ let remote;
14
+ if (remoteName) {
15
+ remote = await getRemote(remoteName);
16
+ if (!remote) {
17
+ process.stderr.write(`Remote "${remoteName}" not found. Run "sig remote list" to see configured remotes.\n`);
18
+ process.exitCode = 4;
19
+ return;
20
+ }
21
+ }
22
+ else {
23
+ const remotes = await getRemotes();
24
+ if (remotes.length === 0) {
25
+ process.stderr.write('No remotes configured. Run "sig remote add <name> <host>" first.\n');
26
+ process.exitCode = 4;
27
+ return;
28
+ }
29
+ if (remotes.length > 1) {
30
+ process.stderr.write('Multiple remotes configured. Specify which one:\n');
31
+ for (const r of remotes) {
32
+ process.stderr.write(` ${r.name} (${r.host})\n`);
33
+ }
34
+ process.exitCode = 1;
35
+ return;
36
+ }
37
+ remote = remotes[0];
38
+ }
39
+ const engine = new SyncEngine(deps.storage, remote);
40
+ const force = flags.force === true;
41
+ const provider = typeof flags.provider === 'string' ? [flags.provider] : undefined;
42
+ process.stderr.write(`${subcommand === 'push' ? 'Pushing' : 'Pulling'} credentials ${subcommand === 'push' ? 'to' : 'from'} "${remote.name}" (${remote.host})...\n`);
43
+ const result = subcommand === 'push'
44
+ ? await engine.push(provider, force)
45
+ : await engine.pull(provider, force);
46
+ // Report results
47
+ const synced = subcommand === 'push' ? result.pushed : result.pulled;
48
+ if (synced.length > 0) {
49
+ process.stderr.write(`Synced: ${synced.join(', ')}\n`);
50
+ }
51
+ if (result.skipped.length > 0) {
52
+ process.stderr.write(`Skipped (conflict): ${result.skipped.join(', ')} — use --force to overwrite\n`);
53
+ }
54
+ if (result.errors.length > 0) {
55
+ for (const e of result.errors) {
56
+ process.stderr.write(`Error (${e.providerId}): ${e.error}\n`);
57
+ }
58
+ process.exitCode = 4;
59
+ }
60
+ // JSON output to stdout
61
+ process.stdout.write(formatJson(result) + '\n');
62
+ }
@@ -0,0 +1,3 @@
1
+ export declare function formatJson(data: unknown): string;
2
+ export declare function formatTable(rows: Record<string, string>[]): string;
3
+ export declare function formatCredentialHeaders(headers: Record<string, string>): string;