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.
- package/LICENSE +21 -0
- package/README.md +393 -0
- package/bin/sig.js +65 -0
- package/dist/auth-manager.d.ts +90 -0
- package/dist/auth-manager.js +262 -0
- package/dist/browser/adapters/playwright.adapter.d.ts +14 -0
- package/dist/browser/adapters/playwright.adapter.js +188 -0
- package/dist/browser/flows/form-login.flow.d.ts +6 -0
- package/dist/browser/flows/form-login.flow.js +35 -0
- package/dist/browser/flows/header-capture.d.ts +23 -0
- package/dist/browser/flows/header-capture.js +104 -0
- package/dist/browser/flows/hybrid-flow.d.ts +37 -0
- package/dist/browser/flows/hybrid-flow.js +104 -0
- package/dist/browser/flows/oauth-consent.flow.d.ts +20 -0
- package/dist/browser/flows/oauth-consent.flow.js +170 -0
- package/dist/cli/commands/doctor.d.ts +6 -0
- package/dist/cli/commands/doctor.js +263 -0
- package/dist/cli/commands/get.d.ts +2 -0
- package/dist/cli/commands/get.js +83 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.js +244 -0
- package/dist/cli/commands/login.d.ts +2 -0
- package/dist/cli/commands/login.js +77 -0
- package/dist/cli/commands/logout.d.ts +2 -0
- package/dist/cli/commands/logout.js +11 -0
- package/dist/cli/commands/providers.d.ts +2 -0
- package/dist/cli/commands/providers.js +30 -0
- package/dist/cli/commands/remote.d.ts +1 -0
- package/dist/cli/commands/remote.js +67 -0
- package/dist/cli/commands/request.d.ts +2 -0
- package/dist/cli/commands/request.js +82 -0
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.js +41 -0
- package/dist/cli/commands/sync.d.ts +2 -0
- package/dist/cli/commands/sync.js +62 -0
- package/dist/cli/formatters.d.ts +3 -0
- package/dist/cli/formatters.js +25 -0
- package/dist/cli/main.d.ts +8 -0
- package/dist/cli/main.js +125 -0
- package/dist/config/generator.d.ts +24 -0
- package/dist/config/generator.js +97 -0
- package/dist/config/loader.d.ts +21 -0
- package/dist/config/loader.js +54 -0
- package/dist/config/schema.d.ts +44 -0
- package/dist/config/schema.js +8 -0
- package/dist/config/validator.d.ts +15 -0
- package/dist/config/validator.js +228 -0
- package/dist/core/errors.d.ts +57 -0
- package/dist/core/errors.js +107 -0
- package/dist/core/interfaces/auth-strategy.d.ts +48 -0
- package/dist/core/interfaces/auth-strategy.js +1 -0
- package/dist/core/interfaces/browser-adapter.d.ts +73 -0
- package/dist/core/interfaces/browser-adapter.js +1 -0
- package/dist/core/interfaces/provider.d.ts +15 -0
- package/dist/core/interfaces/provider.js +1 -0
- package/dist/core/interfaces/storage.d.ts +21 -0
- package/dist/core/interfaces/storage.js +1 -0
- package/dist/core/result.d.ts +21 -0
- package/dist/core/result.js +16 -0
- package/dist/core/types.d.ts +128 -0
- package/dist/core/types.js +6 -0
- package/dist/deps.d.ts +20 -0
- package/dist/deps.js +54 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +37 -0
- package/dist/providers/auto-provision.d.ts +9 -0
- package/dist/providers/auto-provision.js +27 -0
- package/dist/providers/config-loader.d.ts +7 -0
- package/dist/providers/config-loader.js +7 -0
- package/dist/providers/provider-registry.d.ts +19 -0
- package/dist/providers/provider-registry.js +68 -0
- package/dist/storage/cached-storage.d.ts +24 -0
- package/dist/storage/cached-storage.js +57 -0
- package/dist/storage/directory-storage.d.ts +25 -0
- package/dist/storage/directory-storage.js +184 -0
- package/dist/storage/memory-storage.d.ts +14 -0
- package/dist/storage/memory-storage.js +27 -0
- package/dist/strategies/api-token.strategy.d.ts +6 -0
- package/dist/strategies/api-token.strategy.js +63 -0
- package/dist/strategies/basic-auth.strategy.d.ts +6 -0
- package/dist/strategies/basic-auth.strategy.js +41 -0
- package/dist/strategies/cookie.strategy.d.ts +6 -0
- package/dist/strategies/cookie.strategy.js +118 -0
- package/dist/strategies/oauth2.strategy.d.ts +6 -0
- package/dist/strategies/oauth2.strategy.js +134 -0
- package/dist/strategies/registry.d.ts +13 -0
- package/dist/strategies/registry.js +25 -0
- package/dist/sync/remote-config.d.ts +8 -0
- package/dist/sync/remote-config.js +49 -0
- package/dist/sync/sync-engine.d.ts +10 -0
- package/dist/sync/sync-engine.js +96 -0
- package/dist/sync/transports/ssh.d.ts +18 -0
- package/dist/sync/transports/ssh.js +115 -0
- package/dist/sync/types.d.ts +17 -0
- package/dist/sync/types.js +1 -0
- package/dist/utils/duration.d.ts +9 -0
- package/dist/utils/duration.js +34 -0
- package/dist/utils/http.d.ts +4 -0
- package/dist/utils/http.js +10 -0
- package/dist/utils/jwt.d.ts +15 -0
- package/dist/utils/jwt.js +30 -0
- package/package.json +56 -0
- package/src/auth-manager.ts +331 -0
- package/src/browser/adapters/playwright.adapter.ts +247 -0
- package/src/browser/flows/form-login.flow.ts +35 -0
- package/src/browser/flows/header-capture.ts +128 -0
- package/src/browser/flows/hybrid-flow.ts +165 -0
- package/src/browser/flows/oauth-consent.flow.ts +200 -0
- package/src/cli/commands/doctor.ts +301 -0
- package/src/cli/commands/get.ts +96 -0
- package/src/cli/commands/init.ts +289 -0
- package/src/cli/commands/login.ts +94 -0
- package/src/cli/commands/logout.ts +17 -0
- package/src/cli/commands/providers.ts +39 -0
- package/src/cli/commands/remote.ts +71 -0
- package/src/cli/commands/request.ts +97 -0
- package/src/cli/commands/status.ts +48 -0
- package/src/cli/commands/sync.ts +71 -0
- package/src/cli/formatters.ts +31 -0
- package/src/cli/main.ts +144 -0
- package/src/config/generator.ts +122 -0
- package/src/config/loader.ts +70 -0
- package/src/config/schema.ts +75 -0
- package/src/config/validator.ts +281 -0
- package/src/core/errors.ts +182 -0
- package/src/core/interfaces/auth-strategy.ts +65 -0
- package/src/core/interfaces/browser-adapter.ts +81 -0
- package/src/core/interfaces/provider.ts +19 -0
- package/src/core/interfaces/storage.ts +26 -0
- package/src/core/result.ts +24 -0
- package/src/core/types.ts +194 -0
- package/src/deps.ts +80 -0
- package/src/index.ts +109 -0
- package/src/providers/auto-provision.ts +30 -0
- package/src/providers/config-loader.ts +8 -0
- package/src/providers/provider-registry.ts +79 -0
- package/src/storage/cached-storage.ts +72 -0
- package/src/storage/directory-storage.ts +204 -0
- package/src/storage/memory-storage.ts +35 -0
- package/src/strategies/api-token.strategy.ts +87 -0
- package/src/strategies/basic-auth.strategy.ts +64 -0
- package/src/strategies/cookie.strategy.ts +153 -0
- package/src/strategies/oauth2.strategy.ts +178 -0
- package/src/strategies/registry.ts +34 -0
- package/src/sync/remote-config.ts +60 -0
- package/src/sync/sync-engine.ts +113 -0
- package/src/sync/transports/ssh.ts +130 -0
- package/src/sync/types.ts +15 -0
- package/src/utils/duration.ts +34 -0
- package/src/utils/http.ts +11 -0
- package/src/utils/jwt.ts +39 -0
- 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,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,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,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,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,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,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
|
+
}
|