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,94 @@
|
|
|
1
|
+
import type { AuthDeps } from '../../deps.js';
|
|
2
|
+
import type { ApiKeyCredential, BasicCredential } from '../../core/types.js';
|
|
3
|
+
import type { StrategyName } from '../../config/schema.js';
|
|
4
|
+
import { buildStrategyConfig } from '../../config/validator.js';
|
|
5
|
+
import { isOk } from '../../core/result.js';
|
|
6
|
+
import { formatJson } from '../formatters.js';
|
|
7
|
+
|
|
8
|
+
export async function runLogin(
|
|
9
|
+
positionals: string[],
|
|
10
|
+
flags: Record<string, string | boolean>,
|
|
11
|
+
deps: AuthDeps,
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
const url = positionals[0];
|
|
14
|
+
if (!url) {
|
|
15
|
+
process.stderr.write('Usage: sig login <url>\n');
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const baseProvider = deps.authManager.resolveProvider(url);
|
|
21
|
+
|
|
22
|
+
const hasOverrides = flags.strategy !== undefined;
|
|
23
|
+
const provider = hasOverrides
|
|
24
|
+
? { ...baseProvider }
|
|
25
|
+
: baseProvider;
|
|
26
|
+
|
|
27
|
+
if (typeof flags.strategy === 'string') {
|
|
28
|
+
const strategyName = flags.strategy as StrategyName;
|
|
29
|
+
provider.strategy = strategyName;
|
|
30
|
+
provider.strategyConfig = buildStrategyConfig(strategyName);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (hasOverrides) {
|
|
34
|
+
deps.authManager.providerRegistry.register(provider);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof flags.token === 'string') {
|
|
38
|
+
// Read headerName/headerPrefix from the typed strategy config if api-token
|
|
39
|
+
const headerName = provider.strategyConfig.strategy === 'api-token'
|
|
40
|
+
? provider.strategyConfig.headerName ?? 'Authorization'
|
|
41
|
+
: 'Authorization';
|
|
42
|
+
const headerPrefix = provider.strategyConfig.strategy === 'api-token'
|
|
43
|
+
? provider.strategyConfig.headerPrefix ?? 'Bearer'
|
|
44
|
+
: 'Bearer';
|
|
45
|
+
|
|
46
|
+
const credential: ApiKeyCredential = {
|
|
47
|
+
type: 'api-key',
|
|
48
|
+
key: flags.token,
|
|
49
|
+
headerName,
|
|
50
|
+
headerPrefix,
|
|
51
|
+
};
|
|
52
|
+
const result = await deps.authManager.setCredential(provider.id, credential);
|
|
53
|
+
if (!isOk(result)) {
|
|
54
|
+
process.stderr.write(`Error: ${result.error.message}\n`);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
process.stderr.write(`Token stored for "${provider.name}" (${provider.id}).\n`);
|
|
59
|
+
process.stdout.write(formatJson({ provider: provider.id, type: 'api-key' }) + '\n');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof flags.username === 'string' && typeof flags.password === 'string') {
|
|
64
|
+
const credential: BasicCredential = {
|
|
65
|
+
type: 'basic',
|
|
66
|
+
username: flags.username,
|
|
67
|
+
password: flags.password,
|
|
68
|
+
};
|
|
69
|
+
const result = await deps.authManager.setCredential(provider.id, credential);
|
|
70
|
+
if (!isOk(result)) {
|
|
71
|
+
process.stderr.write(`Error: ${result.error.message}\n`);
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
process.stderr.write(`Basic auth credentials stored for "${provider.name}" (${provider.id}).\n`);
|
|
76
|
+
process.stdout.write(formatJson({ provider: provider.id, type: 'basic' }) + '\n');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
process.stderr.write(`Authenticating with "${provider.name}" via browser...\n`);
|
|
81
|
+
const result = await deps.authManager.forceReauth(provider.id);
|
|
82
|
+
if (!isOk(result)) {
|
|
83
|
+
process.stderr.write(`Authentication failed: ${result.error.message}\n`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const status = await deps.authManager.getStatus(provider.id);
|
|
88
|
+
process.stderr.write(`Authenticated with "${provider.name}".\n`);
|
|
89
|
+
process.stdout.write(formatJson({
|
|
90
|
+
provider: provider.id,
|
|
91
|
+
type: result.value.type,
|
|
92
|
+
...(status.expiresAt ? { expiresAt: status.expiresAt } : {}),
|
|
93
|
+
}) + '\n');
|
|
94
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AuthDeps } from '../../deps.js';
|
|
2
|
+
|
|
3
|
+
export async function runLogout(
|
|
4
|
+
positionals: string[],
|
|
5
|
+
flags: Record<string, string | boolean>,
|
|
6
|
+
deps: AuthDeps,
|
|
7
|
+
): Promise<void> {
|
|
8
|
+
const providerId = positionals[0];
|
|
9
|
+
|
|
10
|
+
if (providerId) {
|
|
11
|
+
await deps.authManager.clearCredentials(providerId);
|
|
12
|
+
process.stderr.write(`Credentials cleared for "${providerId}".\n`);
|
|
13
|
+
} else {
|
|
14
|
+
await deps.authManager.clearAll();
|
|
15
|
+
process.stderr.write('All credentials cleared.\n');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AuthDeps } from '../../deps.js';
|
|
2
|
+
import { formatJson, formatTable } from '../formatters.js';
|
|
3
|
+
|
|
4
|
+
export async function runProviders(
|
|
5
|
+
positionals: string[],
|
|
6
|
+
flags: Record<string, string | boolean>,
|
|
7
|
+
deps: AuthDeps,
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
const format = (flags.format as string) ?? (process.stdout.isTTY ? 'table' : 'json');
|
|
10
|
+
const providers = deps.authManager.providerRegistry.list();
|
|
11
|
+
|
|
12
|
+
const statuses = await Promise.all(
|
|
13
|
+
providers.map(p => deps.authManager.getStatus(p.id)),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
if (format === 'json') {
|
|
17
|
+
const output = statuses.map(s => ({
|
|
18
|
+
id: s.id,
|
|
19
|
+
name: s.name,
|
|
20
|
+
strategy: s.strategy,
|
|
21
|
+
configured: s.configured,
|
|
22
|
+
valid: s.valid,
|
|
23
|
+
credentialType: s.credentialType ?? null,
|
|
24
|
+
}));
|
|
25
|
+
process.stdout.write(formatJson(output) + '\n');
|
|
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
|
+
status: s.valid ? 'authenticated' : 'not authenticated',
|
|
36
|
+
}));
|
|
37
|
+
process.stdout.write(formatTable(rows) + '\n');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getRemotes, addRemote, removeRemote } from '../../sync/remote-config.js';
|
|
2
|
+
import type { RemoteConfig } from '../../sync/types.js';
|
|
3
|
+
import { formatJson, formatTable } from '../formatters.js';
|
|
4
|
+
|
|
5
|
+
export async function runRemote(positionals: string[], flags: Record<string, string | boolean>): Promise<void> {
|
|
6
|
+
const subcommand = positionals[0];
|
|
7
|
+
|
|
8
|
+
switch (subcommand) {
|
|
9
|
+
case 'add': {
|
|
10
|
+
const name = positionals[1];
|
|
11
|
+
const host = positionals[2];
|
|
12
|
+
if (!name || !host) {
|
|
13
|
+
process.stderr.write('Usage: sig remote add <name> <host> [--user <user>] [--path <path>] [--ssh-key <key>]\n');
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const remote: RemoteConfig = {
|
|
18
|
+
name,
|
|
19
|
+
type: 'ssh',
|
|
20
|
+
host,
|
|
21
|
+
...(typeof flags.user === 'string' ? { user: flags.user } : {}),
|
|
22
|
+
...(typeof flags.path === 'string' ? { path: flags.path } : {}),
|
|
23
|
+
...(typeof flags['ssh-key'] === 'string' ? { sshKey: flags['ssh-key'] } : {}),
|
|
24
|
+
};
|
|
25
|
+
await addRemote(remote);
|
|
26
|
+
process.stderr.write(`Remote "${name}" added (${host})\n`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
case 'remove': {
|
|
31
|
+
const name = positionals[1];
|
|
32
|
+
if (!name) {
|
|
33
|
+
process.stderr.write('Usage: sig remote remove <name>\n');
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const removed = await removeRemote(name);
|
|
38
|
+
if (removed) {
|
|
39
|
+
process.stderr.write(`Remote "${name}" removed\n`);
|
|
40
|
+
} else {
|
|
41
|
+
process.stderr.write(`Remote "${name}" not found\n`);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
case 'list':
|
|
48
|
+
default: {
|
|
49
|
+
const remotes = await getRemotes();
|
|
50
|
+
if (remotes.length === 0) {
|
|
51
|
+
process.stderr.write('No remotes configured. Use "sig remote add <name> <host>" to add one.\n');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const format = typeof flags.format === 'string' ? flags.format : 'table';
|
|
56
|
+
if (format === 'json') {
|
|
57
|
+
process.stdout.write(formatJson(remotes) + '\n');
|
|
58
|
+
} else {
|
|
59
|
+
const rows = remotes.map(r => ({
|
|
60
|
+
name: r.name,
|
|
61
|
+
type: r.type,
|
|
62
|
+
host: r.host,
|
|
63
|
+
user: r.user ?? '-',
|
|
64
|
+
path: r.path ?? '~/.signet/credentials',
|
|
65
|
+
}));
|
|
66
|
+
process.stdout.write(formatTable(rows) + '\n');
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { AuthDeps } from '../../deps.js';
|
|
2
|
+
import { isOk } from '../../core/result.js';
|
|
3
|
+
import { buildUserAgent } from '../../utils/http.js';
|
|
4
|
+
import { formatJson } from '../formatters.js';
|
|
5
|
+
|
|
6
|
+
export async function runRequest(
|
|
7
|
+
positionals: string[],
|
|
8
|
+
flags: Record<string, string | boolean>,
|
|
9
|
+
deps: AuthDeps,
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
const url = positionals[0];
|
|
12
|
+
if (!url) {
|
|
13
|
+
process.stderr.write('Usage: sig request <url> [--method GET] [--header "Name: Value"] [--body \'{}\']\n');
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = await deps.authManager.getCredentialsByUrl(url);
|
|
19
|
+
if (!isOk(result)) {
|
|
20
|
+
process.stderr.write(`Auth error: ${result.error.message}\n`);
|
|
21
|
+
process.exitCode = 1;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { provider, credential } = result.value;
|
|
26
|
+
const authHeaders = deps.authManager.applyToRequest(provider.id, credential);
|
|
27
|
+
|
|
28
|
+
const requestHeaders: Record<string, string> = {
|
|
29
|
+
'User-Agent': buildUserAgent(),
|
|
30
|
+
...authHeaders,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Parse --header flags (may appear multiple times via positionals workaround, or as single value)
|
|
34
|
+
if (typeof flags.header === 'string') {
|
|
35
|
+
const idx = flags.header.indexOf(':');
|
|
36
|
+
if (idx > 0) {
|
|
37
|
+
requestHeaders[flags.header.slice(0, idx).trim()] = flags.header.slice(idx + 1).trim();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const httpMethod = ((flags.method as string) ?? 'GET').toUpperCase();
|
|
42
|
+
const fetchOptions: RequestInit = { method: httpMethod, headers: requestHeaders };
|
|
43
|
+
|
|
44
|
+
const body = flags.body as string | undefined;
|
|
45
|
+
if (body && ['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
|
|
46
|
+
fetchOptions.body = body;
|
|
47
|
+
if (!requestHeaders['Content-Type']) {
|
|
48
|
+
requestHeaders['Content-Type'] = 'application/json';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(url, fetchOptions);
|
|
54
|
+
const responseBody = await response.text();
|
|
55
|
+
|
|
56
|
+
let formattedBody: string;
|
|
57
|
+
try {
|
|
58
|
+
formattedBody = JSON.stringify(JSON.parse(responseBody), null, 2);
|
|
59
|
+
} catch {
|
|
60
|
+
formattedBody = responseBody;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const format = (flags.format as string) ?? 'json';
|
|
64
|
+
|
|
65
|
+
switch (format) {
|
|
66
|
+
case 'body':
|
|
67
|
+
process.stdout.write(formattedBody + '\n');
|
|
68
|
+
break;
|
|
69
|
+
case 'headers': {
|
|
70
|
+
process.stdout.write(`${response.status} ${response.statusText}\n`);
|
|
71
|
+
response.headers.forEach((value, key) => {
|
|
72
|
+
process.stdout.write(`${key}: ${value}\n`);
|
|
73
|
+
});
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case 'json':
|
|
77
|
+
default: {
|
|
78
|
+
const responseHeaders: Record<string, string> = {};
|
|
79
|
+
response.headers.forEach((value, key) => { responseHeaders[key] = value; });
|
|
80
|
+
process.stdout.write(formatJson({
|
|
81
|
+
status: response.status,
|
|
82
|
+
statusText: response.statusText,
|
|
83
|
+
headers: responseHeaders,
|
|
84
|
+
body: formattedBody,
|
|
85
|
+
}) + '\n');
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
}
|
|
93
|
+
} catch (e: unknown) {
|
|
94
|
+
process.stderr.write(`Request failed: ${(e as Error).message}\n`);
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AuthDeps } from '../../deps.js';
|
|
2
|
+
import { formatJson, formatTable } from '../formatters.js';
|
|
3
|
+
|
|
4
|
+
export async function runStatus(
|
|
5
|
+
positionals: string[],
|
|
6
|
+
flags: Record<string, string | boolean>,
|
|
7
|
+
deps: AuthDeps,
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
const providerId = (flags.provider as string) ?? positionals[0];
|
|
10
|
+
const format = (flags.format as string) ?? (process.stdout.isTTY ? 'table' : 'json');
|
|
11
|
+
|
|
12
|
+
if (providerId) {
|
|
13
|
+
const status = await deps.authManager.getStatus(providerId);
|
|
14
|
+
if (format === 'json') {
|
|
15
|
+
process.stdout.write(formatJson(status) + '\n');
|
|
16
|
+
} else {
|
|
17
|
+
process.stdout.write(formatTable([{
|
|
18
|
+
id: status.id,
|
|
19
|
+
name: status.name,
|
|
20
|
+
strategy: status.strategy,
|
|
21
|
+
valid: status.valid ? 'yes' : 'no',
|
|
22
|
+
type: status.credentialType ?? '-',
|
|
23
|
+
expires: status.expiresInMinutes !== undefined ? `${status.expiresInMinutes}m` : '-',
|
|
24
|
+
}]) + '\n');
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const statuses = await deps.authManager.getAllStatus();
|
|
30
|
+
|
|
31
|
+
if (format === 'json') {
|
|
32
|
+
process.stdout.write(formatJson(statuses) + '\n');
|
|
33
|
+
} else {
|
|
34
|
+
if (statuses.length === 0) {
|
|
35
|
+
process.stderr.write('No providers configured.\n');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const rows = statuses.map(s => ({
|
|
39
|
+
id: s.id,
|
|
40
|
+
name: s.name,
|
|
41
|
+
strategy: s.strategy,
|
|
42
|
+
valid: s.valid ? 'yes' : 'no',
|
|
43
|
+
type: s.credentialType ?? '-',
|
|
44
|
+
expires: s.expiresInMinutes !== undefined ? `${s.expiresInMinutes}m` : '-',
|
|
45
|
+
}));
|
|
46
|
+
process.stdout.write(formatTable(rows) + '\n');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { AuthDeps } from '../../deps.js';
|
|
2
|
+
import { getRemote, getRemotes } from '../../sync/remote-config.js';
|
|
3
|
+
import { SyncEngine } from '../../sync/sync-engine.js';
|
|
4
|
+
import { formatJson } from '../formatters.js';
|
|
5
|
+
|
|
6
|
+
export async function runSync(positionals: string[], flags: Record<string, string | boolean>, deps: AuthDeps): Promise<void> {
|
|
7
|
+
const subcommand = positionals[0];
|
|
8
|
+
|
|
9
|
+
if (subcommand !== 'push' && subcommand !== 'pull') {
|
|
10
|
+
process.stderr.write('Usage: sig sync <push|pull> [remote] [--provider <id>] [--force]\n');
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Resolve remote: explicit name or default (if only one configured)
|
|
16
|
+
const remoteName = positionals[1];
|
|
17
|
+
let remote;
|
|
18
|
+
|
|
19
|
+
if (remoteName) {
|
|
20
|
+
remote = await getRemote(remoteName);
|
|
21
|
+
if (!remote) {
|
|
22
|
+
process.stderr.write(`Remote "${remoteName}" not found. Run "sig remote list" to see configured remotes.\n`);
|
|
23
|
+
process.exitCode = 4;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
const remotes = await getRemotes();
|
|
28
|
+
if (remotes.length === 0) {
|
|
29
|
+
process.stderr.write('No remotes configured. Run "sig remote add <name> <host>" first.\n');
|
|
30
|
+
process.exitCode = 4;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (remotes.length > 1) {
|
|
34
|
+
process.stderr.write('Multiple remotes configured. Specify which one:\n');
|
|
35
|
+
for (const r of remotes) {
|
|
36
|
+
process.stderr.write(` ${r.name} (${r.host})\n`);
|
|
37
|
+
}
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
remote = remotes[0];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const engine = new SyncEngine(deps.storage, remote);
|
|
45
|
+
const force = flags.force === true;
|
|
46
|
+
const provider = typeof flags.provider === 'string' ? [flags.provider] : undefined;
|
|
47
|
+
|
|
48
|
+
process.stderr.write(`${subcommand === 'push' ? 'Pushing' : 'Pulling'} credentials ${subcommand === 'push' ? 'to' : 'from'} "${remote.name}" (${remote.host})...\n`);
|
|
49
|
+
|
|
50
|
+
const result = subcommand === 'push'
|
|
51
|
+
? await engine.push(provider, force)
|
|
52
|
+
: await engine.pull(provider, force);
|
|
53
|
+
|
|
54
|
+
// Report results
|
|
55
|
+
const synced = subcommand === 'push' ? result.pushed : result.pulled;
|
|
56
|
+
if (synced.length > 0) {
|
|
57
|
+
process.stderr.write(`Synced: ${synced.join(', ')}\n`);
|
|
58
|
+
}
|
|
59
|
+
if (result.skipped.length > 0) {
|
|
60
|
+
process.stderr.write(`Skipped (conflict): ${result.skipped.join(', ')} — use --force to overwrite\n`);
|
|
61
|
+
}
|
|
62
|
+
if (result.errors.length > 0) {
|
|
63
|
+
for (const e of result.errors) {
|
|
64
|
+
process.stderr.write(`Error (${e.providerId}): ${e.error}\n`);
|
|
65
|
+
}
|
|
66
|
+
process.exitCode = 4;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// JSON output to stdout
|
|
70
|
+
process.stdout.write(formatJson(result) + '\n');
|
|
71
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function formatJson(data: unknown): string {
|
|
2
|
+
return JSON.stringify(data, null, 2);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function formatTable(rows: Record<string, string>[]): string {
|
|
6
|
+
if (rows.length === 0) return '';
|
|
7
|
+
|
|
8
|
+
const columns = Object.keys(rows[0]);
|
|
9
|
+
const widths = new Map<string, number>();
|
|
10
|
+
|
|
11
|
+
for (const col of columns) {
|
|
12
|
+
let max = col.length;
|
|
13
|
+
for (const row of rows) {
|
|
14
|
+
const len = (row[col] ?? '').length;
|
|
15
|
+
if (len > max) max = len;
|
|
16
|
+
}
|
|
17
|
+
widths.set(col, max);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const header = columns.map(c => c.toUpperCase().padEnd(widths.get(c)!)).join(' ');
|
|
21
|
+
const separator = columns.map(c => '-'.repeat(widths.get(c)!)).join(' ');
|
|
22
|
+
const body = rows.map(row =>
|
|
23
|
+
columns.map(c => (row[c] ?? '').padEnd(widths.get(c)!)).join(' ')
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return [header, separator, ...body].join('\n');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function formatCredentialHeaders(headers: Record<string, string>): string {
|
|
30
|
+
return Object.entries(headers).map(([k, v]) => `${k}: ${v}`).join('\n');
|
|
31
|
+
}
|
package/src/cli/main.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { loadConfig, getConfigPath } from '../config/loader.js';
|
|
3
|
+
import { createAuthDeps } from '../deps.js';
|
|
4
|
+
import { isOk } from '../core/result.js';
|
|
5
|
+
import type { AuthDeps } from '../deps.js';
|
|
6
|
+
|
|
7
|
+
import { runGet } from './commands/get.js';
|
|
8
|
+
import { runLogin } from './commands/login.js';
|
|
9
|
+
import { runStatus } from './commands/status.js';
|
|
10
|
+
import { runLogout } from './commands/logout.js';
|
|
11
|
+
import { runProviders } from './commands/providers.js';
|
|
12
|
+
import { runRequest } from './commands/request.js';
|
|
13
|
+
import { runRemote } from './commands/remote.js';
|
|
14
|
+
import { runSync } from './commands/sync.js';
|
|
15
|
+
import { runInit } from './commands/init.js';
|
|
16
|
+
import { runDoctor } from './commands/doctor.js';
|
|
17
|
+
|
|
18
|
+
interface ParsedArgs {
|
|
19
|
+
command: string;
|
|
20
|
+
positionals: string[];
|
|
21
|
+
flags: Record<string, string | boolean>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function parseArgs(args: string[]): ParsedArgs {
|
|
25
|
+
const firstIsFlag = args[0]?.startsWith('--');
|
|
26
|
+
const command = firstIsFlag ? 'help' : (args[0] ?? 'help');
|
|
27
|
+
const positionals: string[] = [];
|
|
28
|
+
const flags: Record<string, string | boolean> = {};
|
|
29
|
+
|
|
30
|
+
let i = firstIsFlag ? 0 : 1;
|
|
31
|
+
while (i < args.length) {
|
|
32
|
+
const arg = args[i];
|
|
33
|
+
if (arg.startsWith('--')) {
|
|
34
|
+
const key = arg.slice(2);
|
|
35
|
+
const next = args[i + 1];
|
|
36
|
+
if (next !== undefined && !next.startsWith('--')) {
|
|
37
|
+
flags[key] = next;
|
|
38
|
+
i += 2;
|
|
39
|
+
} else {
|
|
40
|
+
flags[key] = true;
|
|
41
|
+
i += 1;
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
positionals.push(arg);
|
|
45
|
+
i += 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { command, positionals, flags };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const HELP = `Usage: sig <command> [options]
|
|
53
|
+
|
|
54
|
+
Commands:
|
|
55
|
+
init Set up Signet configuration (interactive)
|
|
56
|
+
get <provider|url> Get credential headers for a provider or URL
|
|
57
|
+
login <url> Authenticate with a system (browser or token)
|
|
58
|
+
request <url> Make an authenticated HTTP request
|
|
59
|
+
status [provider] Show authentication status
|
|
60
|
+
logout [provider] Clear stored credentials
|
|
61
|
+
providers List configured providers
|
|
62
|
+
remote Manage remote credential stores
|
|
63
|
+
sync Sync credentials with a remote
|
|
64
|
+
doctor Check environment and configuration
|
|
65
|
+
|
|
66
|
+
Global options:
|
|
67
|
+
--format <json|table|header|value|body> Output format
|
|
68
|
+
--help Show this help message
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const DEPS_COMMANDS = new Set(['get', 'login', 'status', 'logout', 'providers', 'request', 'sync']);
|
|
72
|
+
|
|
73
|
+
export async function run(args: string[]): Promise<void> {
|
|
74
|
+
const { command, positionals, flags } = parseArgs(args);
|
|
75
|
+
|
|
76
|
+
if (command === 'help' || flags.help === true) {
|
|
77
|
+
process.stdout.write(HELP);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Commands that don't need deps (run before config exists)
|
|
82
|
+
if (command === 'init') {
|
|
83
|
+
await runInit(positionals, flags);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (command === 'doctor') {
|
|
87
|
+
await runDoctor(positionals, flags);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let deps: AuthDeps | undefined;
|
|
92
|
+
if (DEPS_COMMANDS.has(command)) {
|
|
93
|
+
// First-run detection: check if config file exists before loading
|
|
94
|
+
const configPath = getConfigPath();
|
|
95
|
+
if (!existsSync(configPath)) {
|
|
96
|
+
process.stderr.write(
|
|
97
|
+
'\nWelcome to Signet!\n\n' +
|
|
98
|
+
` No config file found at ${configPath}\n` +
|
|
99
|
+
' Run "sig init" to set up your configuration.\n\n',
|
|
100
|
+
);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const configResult = await loadConfig();
|
|
106
|
+
if (!isOk(configResult)) {
|
|
107
|
+
process.stderr.write(`Config error: ${configResult.error.message}\n`);
|
|
108
|
+
process.exitCode = 1;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
deps = createAuthDeps(configResult.value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
switch (command) {
|
|
115
|
+
case 'get':
|
|
116
|
+
await runGet(positionals, flags, deps!);
|
|
117
|
+
break;
|
|
118
|
+
case 'login':
|
|
119
|
+
await runLogin(positionals, flags, deps!);
|
|
120
|
+
break;
|
|
121
|
+
case 'request':
|
|
122
|
+
await runRequest(positionals, flags, deps!);
|
|
123
|
+
break;
|
|
124
|
+
case 'status':
|
|
125
|
+
await runStatus(positionals, flags, deps!);
|
|
126
|
+
break;
|
|
127
|
+
case 'logout':
|
|
128
|
+
await runLogout(positionals, flags, deps!);
|
|
129
|
+
break;
|
|
130
|
+
case 'providers':
|
|
131
|
+
await runProviders(positionals, flags, deps!);
|
|
132
|
+
break;
|
|
133
|
+
case 'remote':
|
|
134
|
+
await runRemote(positionals, flags);
|
|
135
|
+
break;
|
|
136
|
+
case 'sync':
|
|
137
|
+
await runSync(positionals, flags, deps!);
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
process.stderr.write(`Unknown command: ${command}\n\n`);
|
|
141
|
+
process.stdout.write(HELP);
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
}
|
|
144
|
+
}
|