@ozyman42/ozy-cli 0.4.6-linux-x64.0 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ozy +191 -0
- package/bin/ozy-signing-agent +191 -0
- package/bin/ozy-ssh-keygen +191 -0
- package/bin/ozy-virtual-security-key +191 -0
- package/dist/commands/git/hosts.js +18 -0
- package/dist/commands/git/hosts.js.map +1 -0
- package/dist/commands/git/index.js +8 -0
- package/dist/commands/git/index.js.map +1 -0
- package/dist/commands/git/setup.js +145 -0
- package/dist/commands/git/setup.js.map +1 -0
- package/dist/commands/index.js +13 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/npm/index.js +7 -0
- package/dist/commands/npm/index.js.map +1 -0
- package/dist/commands/npm/setup/git-remote.js +66 -0
- package/dist/commands/npm/setup/git-remote.js.map +1 -0
- package/dist/commands/npm/setup/index.js +168 -0
- package/dist/commands/npm/setup/index.js.map +1 -0
- package/dist/commands/npm/setup/npm-auth.js +62 -0
- package/dist/commands/npm/setup/npm-auth.js.map +1 -0
- package/dist/commands/npm/setup/package-json-validate.js +47 -0
- package/dist/commands/npm/setup/package-json-validate.js.map +1 -0
- package/dist/commands/npm/setup/package-registry.js +20 -0
- package/dist/commands/npm/setup/package-registry.js.map +1 -0
- package/dist/commands/npm/setup/setup.test.js +95 -0
- package/dist/commands/npm/setup/setup.test.js.map +1 -0
- package/dist/commands/npm/setup/trusted-publishing.js +84 -0
- package/dist/commands/npm/setup/trusted-publishing.js.map +1 -0
- package/dist/commands/npm/setup/workflow.js +19 -0
- package/dist/commands/npm/setup/workflow.js.map +1 -0
- package/dist/commands/ssh/get-sk-credential.js +104 -0
- package/dist/commands/ssh/get-sk-credential.js.map +1 -0
- package/dist/commands/ssh/index.js +7 -0
- package/dist/commands/ssh/index.js.map +1 -0
- package/dist/commands/upgrade.js +81 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/common/command.js +15 -0
- package/dist/common/command.js.map +1 -0
- package/dist/common/constants.js +25 -0
- package/dist/common/constants.js.map +1 -0
- package/dist/common/effective-modules-extensions.js +13 -0
- package/dist/common/effective-modules-extensions.js.map +1 -0
- package/dist/common/log.js +4 -0
- package/dist/common/log.js.map +1 -0
- package/dist/common/render-caller-tree.js +20 -0
- package/dist/common/render-caller-tree.js.map +1 -0
- package/dist/entrypoints/ozy-signing-agent.js +48 -0
- package/dist/entrypoints/ozy-signing-agent.js.map +1 -0
- package/dist/entrypoints/ozy-ssh-keygen.js +9 -0
- package/dist/entrypoints/ozy-ssh-keygen.js.map +1 -0
- package/dist/entrypoints/ozy-virtual-security-key.js +18 -0
- package/dist/entrypoints/ozy-virtual-security-key.js.map +1 -0
- package/dist/entrypoints/ozy.js +3 -0
- package/dist/entrypoints/ozy.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/thing.js +4 -0
- package/dist/internal/thing.js.map +1 -0
- package/dist/modules/cli/agent-client/impl.js +109 -0
- package/dist/modules/cli/agent-client/impl.js.map +1 -0
- package/dist/modules/cli/agent-client/interface.js +11 -0
- package/dist/modules/cli/agent-client/interface.js.map +1 -0
- package/dist/modules/cli/git/impl.js +124 -0
- package/dist/modules/cli/git/impl.js.map +1 -0
- package/dist/modules/cli/git/interface.js +3 -0
- package/dist/modules/cli/git/interface.js.map +1 -0
- package/dist/modules/cli/git/submodule/fix.js +257 -0
- package/dist/modules/cli/git/submodule/fix.js.map +1 -0
- package/dist/modules/cli/git/submodule/git-state.js +154 -0
- package/dist/modules/cli/git/submodule/git-state.js.map +1 -0
- package/dist/modules/cli/git/submodule/gitmodules.js +113 -0
- package/dist/modules/cli/git/submodule/gitmodules.js.map +1 -0
- package/dist/modules/cli/git/submodule/printer.js +232 -0
- package/dist/modules/cli/git/submodule/printer.js.map +1 -0
- package/dist/modules/cli/git/submodule/resolve-git-dir.js +16 -0
- package/dist/modules/cli/git/submodule/resolve-git-dir.js.map +1 -0
- package/dist/modules/cli/github/impl.js +137 -0
- package/dist/modules/cli/github/impl.js.map +1 -0
- package/dist/modules/cli/github/interface.js +2 -0
- package/dist/modules/cli/github/interface.js.map +1 -0
- package/dist/modules/cli/index.js +9 -0
- package/dist/modules/cli/index.js.map +1 -0
- package/dist/modules/common/crypto/impl.js +133 -0
- package/dist/modules/common/crypto/impl.js.map +1 -0
- package/dist/modules/common/crypto/interface.js +2 -0
- package/dist/modules/common/crypto/interface.js.map +1 -0
- package/dist/modules/common/index.js +10 -0
- package/dist/modules/common/index.js.map +1 -0
- package/dist/modules/common/kep-map-store/impl.js +42 -0
- package/dist/modules/common/kep-map-store/impl.js.map +1 -0
- package/dist/modules/common/kep-map-store/interface.js +2 -0
- package/dist/modules/common/kep-map-store/interface.js.map +1 -0
- package/dist/modules/common/os-platform/caller-info.js +132 -0
- package/dist/modules/common/os-platform/caller-info.js.map +1 -0
- package/dist/modules/common/os-platform/impl.js +87 -0
- package/dist/modules/common/os-platform/impl.js.map +1 -0
- package/dist/modules/common/os-platform/interface.js +2 -0
- package/dist/modules/common/os-platform/interface.js.map +1 -0
- package/dist/modules/common/os-platform/virtual-hid/index.js +12 -0
- package/dist/modules/common/os-platform/virtual-hid/index.js.map +1 -0
- package/dist/modules/common/os-platform/virtual-hid/linux.js +5 -0
- package/dist/modules/common/os-platform/virtual-hid/linux.js.map +1 -0
- package/dist/modules/common/os-platform/virtual-hid/mac.js +119 -0
- package/dist/modules/common/os-platform/virtual-hid/mac.js.map +1 -0
- package/dist/modules/common/os-platform/virtual-hid/windows.js +5 -0
- package/dist/modules/common/os-platform/virtual-hid/windows.js.map +1 -0
- package/dist/modules/common/ssh-config/impl.js +116 -0
- package/dist/modules/common/ssh-config/impl.js.map +1 -0
- package/dist/modules/common/ssh-config/interface.js +10 -0
- package/dist/modules/common/ssh-config/interface.js.map +1 -0
- package/dist/modules/ssh-agent/index.js +8 -0
- package/dist/modules/ssh-agent/index.js.map +1 -0
- package/dist/modules/ssh-agent/session/impl.js +265 -0
- package/dist/modules/ssh-agent/session/impl.js.map +1 -0
- package/dist/modules/ssh-agent/session/interface-rpc.js +20 -0
- package/dist/modules/ssh-agent/session/interface-rpc.js.map +1 -0
- package/dist/modules/ssh-agent/session/interface.js +32 -0
- package/dist/modules/ssh-agent/session/interface.js.map +1 -0
- package/dist/modules/ssh-agent/session/passkey-prf-page.js +204 -0
- package/dist/modules/ssh-agent/session/passkey-prf-page.js.map +1 -0
- package/dist/modules/ssh-agent/session/prf-flow.js +103 -0
- package/dist/modules/ssh-agent/session/prf-flow.js.map +1 -0
- package/dist/modules/ssh-agent/ssh-agent/impl.js +117 -0
- package/dist/modules/ssh-agent/ssh-agent/impl.js.map +1 -0
- package/dist/modules/ssh-agent/ssh-agent/interface.js +2 -0
- package/dist/modules/ssh-agent/ssh-agent/interface.js.map +1 -0
- package/dist/scripts/build.js +412 -0
- package/dist/scripts/build.js.map +1 -0
- package/dist/scripts/check-npm-version.js +8 -0
- package/dist/scripts/check-npm-version.js.map +1 -0
- package/dist/scripts/kill-existing-agent.js +21 -0
- package/dist/scripts/kill-existing-agent.js.map +1 -0
- package/dist/scripts/publish.js +46 -0
- package/dist/scripts/publish.js.map +1 -0
- package/package.json +52 -11
- package/src/commands/git/hosts.ts +20 -0
- package/src/commands/git/index.ts +9 -0
- package/src/commands/git/setup.ts +182 -0
- package/src/commands/index.ts +15 -0
- package/src/commands/npm/index.ts +8 -0
- package/src/commands/npm/setup/git-remote.ts +78 -0
- package/src/commands/npm/setup/index.ts +189 -0
- package/src/commands/npm/setup/npm-auth.ts +58 -0
- package/src/commands/npm/setup/package-json-validate.ts +68 -0
- package/src/commands/npm/setup/package-registry.ts +24 -0
- package/src/commands/npm/setup/publish-workflow.yml +35 -0
- package/src/commands/npm/setup/setup.test.ts +117 -0
- package/src/commands/npm/setup/trusted-publishing.ts +106 -0
- package/src/commands/npm/setup/workflow.ts +20 -0
- package/src/commands/ssh/get-sk-credential.ts +110 -0
- package/src/commands/ssh/index.ts +8 -0
- package/src/commands/upgrade.ts +90 -0
- package/src/common/command.ts +16 -0
- package/src/common/constants.ts +26 -0
- package/src/common/effective-modules-extensions.ts +24 -0
- package/src/common/log.ts +3 -0
- package/src/common/render-caller-tree.ts +22 -0
- package/src/entrypoints/ozy-signing-agent.ts +48 -0
- package/src/entrypoints/ozy-ssh-keygen.ts +10 -0
- package/src/entrypoints/ozy-virtual-security-key.ts +21 -0
- package/src/entrypoints/ozy.ts +2 -0
- package/src/index.ts +1 -0
- package/src/internal/thing.ts +3 -0
- package/src/modules/cli/agent-client/impl.ts +152 -0
- package/src/modules/cli/agent-client/interface.ts +17 -0
- package/src/modules/cli/git/impl.ts +133 -0
- package/src/modules/cli/git/interface.ts +23 -0
- package/src/modules/cli/git/submodule/fix.ts +298 -0
- package/src/modules/cli/git/submodule/git-state.ts +217 -0
- package/src/modules/cli/git/submodule/gitmodules.ts +126 -0
- package/src/modules/cli/git/submodule/printer.ts +256 -0
- package/src/modules/cli/git/submodule/resolve-git-dir.ts +14 -0
- package/src/modules/cli/github/impl.ts +169 -0
- package/src/modules/cli/github/interface.ts +29 -0
- package/src/modules/cli/index.ts +16 -0
- package/src/modules/common/crypto/impl.ts +173 -0
- package/src/modules/common/crypto/interface.ts +20 -0
- package/src/modules/common/index.ts +19 -0
- package/src/modules/common/kep-map-store/impl.ts +47 -0
- package/src/modules/common/kep-map-store/interface.ts +9 -0
- package/src/modules/common/os-platform/caller-info.ts +152 -0
- package/src/modules/common/os-platform/impl.ts +86 -0
- package/src/modules/common/os-platform/interface.ts +16 -0
- package/src/modules/common/os-platform/virtual-hid/index.ts +12 -0
- package/src/modules/common/os-platform/virtual-hid/linux.ts +7 -0
- package/src/modules/common/os-platform/virtual-hid/mac.ts +150 -0
- package/src/modules/common/os-platform/virtual-hid/windows.ts +7 -0
- package/src/modules/common/ssh-config/impl.ts +130 -0
- package/src/modules/common/ssh-config/interface.ts +33 -0
- package/src/modules/ssh-agent/index.ts +13 -0
- package/src/modules/ssh-agent/session/agent-notes.md +210 -0
- package/src/modules/ssh-agent/session/design.md +2 -0
- package/src/modules/ssh-agent/session/html-refactor.md +23 -0
- package/src/modules/ssh-agent/session/impl.ts +328 -0
- package/src/modules/ssh-agent/session/interface-rpc.ts +24 -0
- package/src/modules/ssh-agent/session/interface.ts +62 -0
- package/src/modules/ssh-agent/session/passkey-prf-page.ts +224 -0
- package/src/modules/ssh-agent/session/prf-flow.ts +152 -0
- package/src/modules/ssh-agent/ssh-agent/impl.ts +136 -0
- package/src/modules/ssh-agent/ssh-agent/interface.ts +9 -0
- package/src/scripts/build.ts +506 -0
- package/src/scripts/check-npm-version.ts +7 -0
- package/src/scripts/command-launcher.js.ejs +168 -0
- package/src/scripts/kill-existing-agent.ts +22 -0
- package/src/scripts/publish.ts +47 -0
- package/src/scripts/windows-bin-shims.md +1104 -0
- package/multi-call-binary +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { validatePackageJson } from './package-json-validate';
|
|
6
|
+
import { parseGithubOwnerRepo } from './git-remote';
|
|
7
|
+
import { workflowExists, createWorkflow } from './workflow';
|
|
8
|
+
import { Option } from "effect";
|
|
9
|
+
|
|
10
|
+
// ── validatePackageJson ──────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
describe('validatePackageJson', () => {
|
|
13
|
+
const base = {
|
|
14
|
+
name: '@scope/pkg',
|
|
15
|
+
version: '1.0.0',
|
|
16
|
+
repository: { type: 'git', url: 'git+https://github.com/owner/repo.git' },
|
|
17
|
+
publishConfig: { access: 'public' as const, provenance: true },
|
|
18
|
+
scripts: { build: 'bun run build' },
|
|
19
|
+
files: ['dist'],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
test('returns no issues for a valid package', () => {
|
|
23
|
+
expect(validatePackageJson(base)).toEqual([]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('fatal: missing version', () => {
|
|
27
|
+
const issues = validatePackageJson({ ...base, version: '' });
|
|
28
|
+
expect(issues.some(i => i.field === 'version' && i.fatal)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('fatal: missing build script', () => {
|
|
32
|
+
const issues = validatePackageJson({ ...base, scripts: {} });
|
|
33
|
+
expect(issues.some(i => i.field === 'scripts.build' && i.fatal)).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('non-fatal: missing repository', () => {
|
|
37
|
+
const issues = validatePackageJson({ ...base, repository: undefined as any });
|
|
38
|
+
expect(issues.some(i => i.field === 'repository' && !i.fatal)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('non-fatal: invalid repository url', () => {
|
|
42
|
+
const issues = validatePackageJson({ ...base, repository: { type: 'git', url: 'git+.git' } });
|
|
43
|
+
expect(issues.some(i => i.field === 'repository.url' && !i.fatal)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('non-fatal: publishConfig.access not public', () => {
|
|
47
|
+
const issues = validatePackageJson({ ...base, publishConfig: { access: 'restricted', provenance: true } });
|
|
48
|
+
expect(issues.some(i => i.field === 'publishConfig.access')).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('non-fatal: publishConfig.provenance not true', () => {
|
|
52
|
+
const issues = validatePackageJson({ ...base, publishConfig: { access: 'public', provenance: false } });
|
|
53
|
+
expect(issues.some(i => i.field === 'publishConfig.provenance')).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('non-fatal: dist not in files', () => {
|
|
57
|
+
const issues = validatePackageJson({ ...base, files: ['src'] });
|
|
58
|
+
expect(issues.some(i => i.field === 'files')).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ── parseGithubOwnerRepo ─────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
describe('parseGithubOwnerRepo', () => {
|
|
65
|
+
test('parses https url', () => {
|
|
66
|
+
expect(parseGithubOwnerRepo('https://github.com/owner/repo.git')).toEqual(Option.some({ owner: 'owner', repo: 'repo' }));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('parses https url without .git', () => {
|
|
70
|
+
expect(parseGithubOwnerRepo('https://github.com/owner/repo')).toEqual(Option.some({ owner: 'owner', repo: 'repo' }));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('parses standard ssh url', () => {
|
|
74
|
+
expect(parseGithubOwnerRepo('git@github.com:owner/repo.git')).toEqual(Option.some({ owner: 'owner', repo: 'repo' }));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('parses ssh alias url', () => {
|
|
78
|
+
expect(parseGithubOwnerRepo('git@github-ozyman42:ozyman42/test.git')).toEqual(Option.some({ owner: 'ozyman42', repo: 'test' }));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('returns null for unrecognised url', () => {
|
|
82
|
+
expect(parseGithubOwnerRepo('not-a-url')).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ── workflow helpers ─────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
describe('workflow', () => {
|
|
89
|
+
let tmpDir: string;
|
|
90
|
+
|
|
91
|
+
beforeEach(async () => {
|
|
92
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ozy-test-'));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
afterEach(async () => {
|
|
96
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('workflowExists returns false when missing', async () => {
|
|
100
|
+
expect(await workflowExists(tmpDir)).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('createWorkflow creates a valid yaml file', async () => {
|
|
104
|
+
await createWorkflow(tmpDir);
|
|
105
|
+
expect(await workflowExists(tmpDir)).toBe(true);
|
|
106
|
+
const content = await fs.readFile(path.join(tmpDir, '.github/workflows/publish.yml'), 'utf8');
|
|
107
|
+
expect(content).toContain('npm publish dist/');
|
|
108
|
+
expect(content).toContain('id-token: write');
|
|
109
|
+
expect(content).toContain('bun run build');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('createWorkflow is idempotent', async () => {
|
|
113
|
+
await createWorkflow(tmpDir);
|
|
114
|
+
await createWorkflow(tmpDir);
|
|
115
|
+
expect(await workflowExists(tmpDir)).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { $ } from 'bun';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { Effect } from 'effect';
|
|
4
|
+
import { log } from '@/common/log';
|
|
5
|
+
|
|
6
|
+
function encodePackageName(name: string): string {
|
|
7
|
+
if (!name.startsWith('@')) return encodeURIComponent(name);
|
|
8
|
+
const slash = name.indexOf('/');
|
|
9
|
+
return `@${name.slice(1, slash)}%2F${name.slice(slash + 1)}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function promptPasscode(): Promise<string> {
|
|
13
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
14
|
+
return new Promise(resolve => {
|
|
15
|
+
rl.question(' enter the passcode from the browser: ', answer => {
|
|
16
|
+
rl.close();
|
|
17
|
+
resolve(answer.trim());
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function registryPost(
|
|
23
|
+
url: string,
|
|
24
|
+
body: string,
|
|
25
|
+
authToken: string,
|
|
26
|
+
{ existingOtp, alreadyExistsStatus }: { existingOtp?: string; alreadyExistsStatus?: number } = {},
|
|
27
|
+
): Promise<{ otp?: string; alreadyExists: boolean }> {
|
|
28
|
+
const headers: Record<string, string> = {
|
|
29
|
+
Authorization: `Bearer ${authToken}`,
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const post = (otp?: string) => fetch(url, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: otp ? { ...headers, 'npm-otp': otp } : headers,
|
|
36
|
+
body,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const check = (res: Response) => {
|
|
40
|
+
if (res.ok) return true;
|
|
41
|
+
if (alreadyExistsStatus && res.status === alreadyExistsStatus) return true;
|
|
42
|
+
return false;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (existingOtp) {
|
|
46
|
+
const res = await post(existingOtp);
|
|
47
|
+
if (check(res)) return { otp: existingOtp, alreadyExists: !res.ok };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let res = await post();
|
|
51
|
+
if (check(res)) return { otp: undefined, alreadyExists: !res.ok };
|
|
52
|
+
|
|
53
|
+
if (res.status === 401) {
|
|
54
|
+
const notice = res.headers.get('npm-notice') ?? '';
|
|
55
|
+
const loginUrl = notice.match(/https:\/\/www\.npmjs\.com\/login\/[a-f0-9-]+/)?.[0];
|
|
56
|
+
if (!loginUrl) throw new Error('2FA required but could not parse login URL');
|
|
57
|
+
await $`open ${loginUrl}`.quiet().nothrow();
|
|
58
|
+
const otp = await promptPasscode();
|
|
59
|
+
res = await post(otp);
|
|
60
|
+
if (check(res)) return { otp, alreadyExists: !res.ok };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
throw new Error(`${res.status} ${await res.text()}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function addTrustedPublisher(
|
|
67
|
+
name: string,
|
|
68
|
+
ownerRepo: string,
|
|
69
|
+
token: string,
|
|
70
|
+
): Effect.Effect<{ alreadyConfigured: boolean; otp?: string }, string> {
|
|
71
|
+
const encoded = encodePackageName(name);
|
|
72
|
+
log(' setting up (may require 2FA)...');
|
|
73
|
+
const body = JSON.stringify([{
|
|
74
|
+
type: 'github',
|
|
75
|
+
claims: { repository: ownerRepo, workflow_ref: { file: 'pipeline.yml' } },
|
|
76
|
+
permissions: ['createPackage'],
|
|
77
|
+
}]);
|
|
78
|
+
return Effect.tryPromise({
|
|
79
|
+
try: async () => {
|
|
80
|
+
const { otp, alreadyExists } = await registryPost(
|
|
81
|
+
`https://registry.npmjs.org/-/package/${encoded}/trust`, body, token,
|
|
82
|
+
{ alreadyExistsStatus: 409 },
|
|
83
|
+
);
|
|
84
|
+
return { alreadyConfigured: alreadyExists, otp };
|
|
85
|
+
},
|
|
86
|
+
catch: (e) => `trust setup failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function setPackageAccessPolicy(
|
|
91
|
+
name: string,
|
|
92
|
+
token: string,
|
|
93
|
+
existingOtp?: string,
|
|
94
|
+
): Effect.Effect<void, string> {
|
|
95
|
+
const encoded = encodePackageName(name);
|
|
96
|
+
const body = JSON.stringify({ publish_requires_tfa: true, automation_token_overrides_tfa: false });
|
|
97
|
+
return Effect.tryPromise({
|
|
98
|
+
try: async () => {
|
|
99
|
+
await registryPost(
|
|
100
|
+
`https://registry.npmjs.org/-/package/${encoded}/access`, body, token,
|
|
101
|
+
{ existingOtp },
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
catch: (e) => `access policy setup failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import WORKFLOW_TEMPLATE from './publish-workflow.yml' with { type: 'text' };
|
|
4
|
+
|
|
5
|
+
const WORKFLOW_PATH = '.github/workflows/publish.yml';
|
|
6
|
+
|
|
7
|
+
export async function workflowExists(repoRoot: string): Promise<boolean> {
|
|
8
|
+
try {
|
|
9
|
+
await fs.access(path.join(repoRoot, WORKFLOW_PATH));
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function createWorkflow(repoRoot: string): Promise<void> {
|
|
17
|
+
const fullPath = path.join(repoRoot, WORKFLOW_PATH);
|
|
18
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
19
|
+
await fs.writeFile(fullPath, WORKFLOW_TEMPLATE, 'utf8');
|
|
20
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { makeCommand } from "@/common/command";
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import { log } from "@/common/log";
|
|
6
|
+
import { selectFromList } from "@ozyman42/interactive-cli-select";
|
|
7
|
+
import type { EffectGen } from "effective-modules";
|
|
8
|
+
|
|
9
|
+
const OPENSSH_MAGIC = Buffer.from('openssh-key-v1\0', 'utf8');
|
|
10
|
+
|
|
11
|
+
function isSkPublicKey(pubKeyPath: string): boolean {
|
|
12
|
+
try {
|
|
13
|
+
const keyType = fs.readFileSync(pubKeyPath, 'utf8').trim().split(/\s+/)[0] ?? '';
|
|
14
|
+
return keyType.startsWith('sk-');
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseCredentialId(privateKeyPath: string): string {
|
|
21
|
+
const base64 = fs.readFileSync(privateKeyPath, 'utf8')
|
|
22
|
+
.split('\n')
|
|
23
|
+
.filter(line => !line.startsWith('-----'))
|
|
24
|
+
.join('');
|
|
25
|
+
const buf = Buffer.from(base64, 'base64');
|
|
26
|
+
|
|
27
|
+
if (!buf.subarray(0, OPENSSH_MAGIC.length).equals(OPENSSH_MAGIC)) {
|
|
28
|
+
throw new Error('Not an OpenSSH private key file.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let off = OPENSSH_MAGIC.length;
|
|
32
|
+
|
|
33
|
+
function readString(): Buffer {
|
|
34
|
+
if (off + 4 > buf.length) throw new Error('Unexpected end of buffer.');
|
|
35
|
+
const len = buf.readUInt32BE(off); off += 4;
|
|
36
|
+
if (off + len > buf.length) throw new Error('Buffer underflow.');
|
|
37
|
+
const chunk = buf.subarray(off, off + len); off += len;
|
|
38
|
+
return chunk;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readUint32(): number {
|
|
42
|
+
if (off + 4 > buf.length) throw new Error('Unexpected end of buffer.');
|
|
43
|
+
const val = buf.readUInt32BE(off); off += 4;
|
|
44
|
+
return val;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const cipherName = readString().toString('utf8');
|
|
48
|
+
if (cipherName !== 'none') throw new Error('Encrypted private keys are not supported.');
|
|
49
|
+
readString(); // kdf name
|
|
50
|
+
readString(); // kdf options
|
|
51
|
+
|
|
52
|
+
if (readUint32() !== 1) throw new Error('Expected exactly 1 key.');
|
|
53
|
+
readString(); // public key blob
|
|
54
|
+
|
|
55
|
+
const pb = readString(); // private key block
|
|
56
|
+
let p = 0;
|
|
57
|
+
|
|
58
|
+
function pbString(): Buffer {
|
|
59
|
+
if (p + 4 > pb.length) throw new Error('Unexpected end of private block.');
|
|
60
|
+
const len = pb.readUInt32BE(p); p += 4;
|
|
61
|
+
if (p + len > pb.length) throw new Error('Private block underflow.');
|
|
62
|
+
const chunk = pb.subarray(p, p + len); p += len;
|
|
63
|
+
return chunk;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const check1 = pb.readUInt32BE(p); p += 4;
|
|
67
|
+
const check2 = pb.readUInt32BE(p); p += 4;
|
|
68
|
+
if (check1 !== check2) throw new Error('Private key integrity check failed — key may be encrypted or corrupt.');
|
|
69
|
+
|
|
70
|
+
const keyType = pbString().toString('utf8');
|
|
71
|
+
if (keyType.includes('ecdsa')) {
|
|
72
|
+
pbString(); // curve identifier
|
|
73
|
+
pbString(); // ec_point
|
|
74
|
+
} else if (keyType.includes('ed25519')) {
|
|
75
|
+
pbString(); // public key (32 bytes)
|
|
76
|
+
} else {
|
|
77
|
+
throw new Error(`Unsupported security key type: ${keyType}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
pbString(); // application string (e.g. 'ssh:')
|
|
81
|
+
p += 1; // flags byte
|
|
82
|
+
|
|
83
|
+
return pbString().toString('hex');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const skCredential = makeCommand('sk-credential', 'get credential id of a given ssh security key pointer', () =>
|
|
87
|
+
Effect.gen(function* (): EffectGen<void, never> {
|
|
88
|
+
// Effect v3 → v4 shim: Effect.async was renamed to Effect.callback
|
|
89
|
+
(Effect as any).async = (Effect as any).callback;
|
|
90
|
+
|
|
91
|
+
const cwd = process.cwd();
|
|
92
|
+
const skKeys = fs.readdirSync(cwd).filter(name =>
|
|
93
|
+
!name.endsWith('.pub') && isSkPublicKey(path.join(cwd, `${name}.pub`))
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (skKeys.length === 0) {
|
|
97
|
+
log('No SSH security keys found.');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const choice = yield* selectFromList({
|
|
102
|
+
options: skKeys,
|
|
103
|
+
getKey: o => o,
|
|
104
|
+
renderOption: o => o,
|
|
105
|
+
}).pipe(Effect.orDie);
|
|
106
|
+
|
|
107
|
+
const credentialId = parseCredentialId(path.join(cwd, choice));
|
|
108
|
+
log(`Credential ID: ${credentialId}`);
|
|
109
|
+
})
|
|
110
|
+
);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { Effect, Option } from "effect";
|
|
4
|
+
import { makeCommand } from "@/common/command";
|
|
5
|
+
import { CLI_CMD_NAME, PACKAGE_NAME } from "@/common/constants";
|
|
6
|
+
|
|
7
|
+
type PackageManager = "bun" | "pnpm" | "npm";
|
|
8
|
+
type InstallScope = "global" | "local";
|
|
9
|
+
|
|
10
|
+
interface InstallInfo {
|
|
11
|
+
pm: PackageManager;
|
|
12
|
+
scope: InstallScope;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function findLockFile(dir: string): Option.Option<PackageManager> {
|
|
17
|
+
let current = dir;
|
|
18
|
+
while (true) {
|
|
19
|
+
if (existsSync(join(current, "bun.lock")) || existsSync(join(current, "bun.lockb"))) return Option.some("bun" as PackageManager);
|
|
20
|
+
if (existsSync(join(current, "pnpm-lock.yaml"))) return Option.some("pnpm" as PackageManager);
|
|
21
|
+
if (existsSync(join(current, "package-lock.json"))) return Option.some("npm" as PackageManager);
|
|
22
|
+
const parent = dirname(current);
|
|
23
|
+
if (parent === current) return Option.none();
|
|
24
|
+
current = parent;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function detectInstall(binaryPath: string): Option.Option<InstallInfo> {
|
|
29
|
+
if (binaryPath.includes("/.bun/bin/")) {
|
|
30
|
+
return Option.some({ pm: "bun", scope: "global" } as InstallInfo);
|
|
31
|
+
}
|
|
32
|
+
if (binaryPath.includes("/pnpm/")) {
|
|
33
|
+
return Option.some({ pm: "pnpm", scope: "global" } as InstallInfo);
|
|
34
|
+
}
|
|
35
|
+
const localMatch = binaryPath.match(/^(.+\/node_modules)\/.bin\//);
|
|
36
|
+
if (localMatch) {
|
|
37
|
+
const projectRoot = dirname(localMatch[1]!);
|
|
38
|
+
const pm = Option.getOrElse(findLockFile(projectRoot), () => "npm" as PackageManager);
|
|
39
|
+
return Option.some({ pm, scope: "local", cwd: projectRoot } as InstallInfo);
|
|
40
|
+
}
|
|
41
|
+
return Option.none();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildUpgradeCommand(info: InstallInfo): string[] {
|
|
45
|
+
const pkg = `${PACKAGE_NAME}@latest`;
|
|
46
|
+
if (info.scope === "global") {
|
|
47
|
+
if (info.pm === "bun") return ["bun", "add", "--no-cache", "-g", pkg];
|
|
48
|
+
if (info.pm === "pnpm") return ["pnpm", "add", "-g", pkg];
|
|
49
|
+
return ["npm", "install", "-g", pkg];
|
|
50
|
+
}
|
|
51
|
+
if (info.pm === "bun") return ["bun", "add", "--no-cache", pkg];
|
|
52
|
+
if (info.pm === "pnpm") return ["pnpm", "add", pkg];
|
|
53
|
+
return ["npm", "install", pkg];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function whichOzy(): Effect.Effect<string, string> {
|
|
57
|
+
return Effect.tryPromise({
|
|
58
|
+
try: async () => {
|
|
59
|
+
const proc = Bun.spawn(["which", CLI_CMD_NAME], { stdout: "pipe", stderr: "pipe" });
|
|
60
|
+
const code = await proc.exited;
|
|
61
|
+
if (code !== 0) throw new Error(`${CLI_CMD_NAME} binary not found — install with: bun add --no-cache -g ${PACKAGE_NAME}@latest`);
|
|
62
|
+
return (await new Response(proc.stdout).text()).trim();
|
|
63
|
+
},
|
|
64
|
+
catch: (e) => e instanceof Error ? e.message : String(e),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const upgrade = makeCommand("upgrade", `upgrade ${CLI_CMD_NAME} to the latest version`, () =>
|
|
69
|
+
Effect.gen(function* () {
|
|
70
|
+
const binaryPath = yield* whichOzy();
|
|
71
|
+
|
|
72
|
+
const infoOption = detectInstall(binaryPath);
|
|
73
|
+
if (Option.isNone(infoOption))
|
|
74
|
+
return yield* Effect.fail(`unknown-install: could not detect package manager from binary path: ${binaryPath}\nUpgrade manually with your package manager.`);
|
|
75
|
+
const info = infoOption.value;
|
|
76
|
+
|
|
77
|
+
const cmd = buildUpgradeCommand(info);
|
|
78
|
+
console.log(`Detected: ${info.pm} (${info.scope})`);
|
|
79
|
+
console.log(`Running: ${cmd.join(" ")}`);
|
|
80
|
+
|
|
81
|
+
const proc = Bun.spawn(cmd, {
|
|
82
|
+
cwd: info.cwd ?? process.cwd(),
|
|
83
|
+
stdout: "inherit",
|
|
84
|
+
stderr: "inherit",
|
|
85
|
+
});
|
|
86
|
+
const code = yield* Effect.promise(() => proc.exited);
|
|
87
|
+
if (code !== 0)
|
|
88
|
+
yield* Effect.fail(`install-failed: ${cmd[0]} exited with code ${code}`);
|
|
89
|
+
})
|
|
90
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { Effect, Result } from 'effect';
|
|
3
|
+
import { log } from './log';
|
|
4
|
+
|
|
5
|
+
export function makeCommand(name: string, description: string,
|
|
6
|
+
action: () => Effect.Effect<void, string>): Command {
|
|
7
|
+
return new Command(name)
|
|
8
|
+
.description(description)
|
|
9
|
+
.action(async () => {
|
|
10
|
+
const result = await Effect.runPromise(action().pipe(Effect.result));
|
|
11
|
+
if (Result.isFailure(result)) {
|
|
12
|
+
log(`✗ ${result.failure}`);
|
|
13
|
+
}
|
|
14
|
+
process.exit();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { version, name } from "../../package.json";
|
|
3
|
+
|
|
4
|
+
export const PACKAGE_NAME = name;
|
|
5
|
+
export const FUTURE_TOOL_NAME = "jive";
|
|
6
|
+
const CLI_DIR = path.dirname(process.execPath);
|
|
7
|
+
const commandPath = (commandName: string) =>
|
|
8
|
+
path.resolve(CLI_DIR, process.platform === "win32" ? `${commandName}.exe` : commandName);
|
|
9
|
+
export const CLI_CMD_NAME = "ozy";
|
|
10
|
+
export const AGENT_CMD_NAME = "ozy-signing-agent";
|
|
11
|
+
export const AGENT_CMD_PATH = commandPath(AGENT_CMD_NAME);
|
|
12
|
+
export const CLI_CMD_PATH = commandPath(CLI_CMD_NAME);
|
|
13
|
+
export const AGENT_SOCK_FILE_PATH = path.resolve(CLI_DIR, `${AGENT_CMD_NAME}.sock`);
|
|
14
|
+
export const AGENT_LOG_FILE_PATH = path.resolve(CLI_DIR, `${AGENT_CMD_NAME}.log`);
|
|
15
|
+
export const AGENT_PID_FILE_PATH = path.resolve(CLI_DIR, `${AGENT_CMD_NAME}.pid`);
|
|
16
|
+
export const AGENT_PORT_FILE_PATH = path.resolve(CLI_DIR, `${AGENT_CMD_NAME}.port`);
|
|
17
|
+
export const CRED_ID_COMMENT_PREFIX = 'credentialId:';
|
|
18
|
+
export const CURRENT_VERSION = version;
|
|
19
|
+
export const VIRTUAL_KEY_CMD_NAME = "ozy-virtual-security-key";
|
|
20
|
+
export const SSH_KEYGEN_CMD_NAME = "ozy-ssh-keygen";
|
|
21
|
+
export const SSH_KEYGEN_CMD_PATH = commandPath(SSH_KEYGEN_CMD_NAME);
|
|
22
|
+
export const VIRTUAL_KEY_LOG_FILE_PATH = path.resolve(CLI_DIR, `${VIRTUAL_KEY_CMD_NAME}.log`);
|
|
23
|
+
export const DEFAULT_SESSION_TIMEOUT_SECONDS = 120;
|
|
24
|
+
export const STANDARD_REMOTE_USER = "git";
|
|
25
|
+
export const STANDARD_REMOTE_HOST = "github.com";
|
|
26
|
+
export const STANDARD_REMOTE_PREFIX = `${STANDARD_REMOTE_USER}@${STANDARD_REMOTE_HOST}:`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type EffectGen } from "effective-modules";
|
|
2
|
+
import { type Context, Effect } from "effect";
|
|
3
|
+
|
|
4
|
+
type MethodsRequireSelf<S extends Context.Service<any, any>> = {
|
|
5
|
+
[k in keyof S["Service"]]:
|
|
6
|
+
S["Service"][k] extends Function ?
|
|
7
|
+
ReturnType<S["Service"][k]> extends EffectGen<infer A, infer E, infer R> ?
|
|
8
|
+
(...args: Parameters<S["Service"][k]>) => Effect.Effect<A, E, R | S["Identifier"]>
|
|
9
|
+
: ReturnType<S["Service"][k]> extends Effect.Effect<infer A, infer E, infer R> ?
|
|
10
|
+
(...args: Parameters<S["Service"][k]>) => Effect.Effect<A, E, R | S["Identifier"]>
|
|
11
|
+
: never
|
|
12
|
+
: never;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function using<S extends Context.Service<any, any>>(service: S): MethodsRequireSelf<S> {
|
|
16
|
+
return new Proxy({}, {
|
|
17
|
+
get(_target, prop, _receiver) {
|
|
18
|
+
return Effect.fn(function*(...args: any[]) {
|
|
19
|
+
const instance = yield* service;
|
|
20
|
+
return yield* instance[prop](...args);
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
}) as any;
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CallerProcess } from "@/modules/common/os-platform/interface";
|
|
2
|
+
import { Option } from "effect";
|
|
3
|
+
|
|
4
|
+
const CYAN = '\x1b[36m';
|
|
5
|
+
const DIM = '\x1b[2m';
|
|
6
|
+
const RESET = '\x1b[0m';
|
|
7
|
+
|
|
8
|
+
function fmtNode(node: CallerProcess): string {
|
|
9
|
+
const dir = Option.getOrElse(() => '?')(node.directory);
|
|
10
|
+
return `${CYAN}${node.command}${RESET} ${DIM}${dir}${RESET}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function renderCallerTree(chain: CallerProcess[]): string {
|
|
14
|
+
if (chain.length === 0) return '(empty)';
|
|
15
|
+
const lines: string[] = [fmtNode(chain[0]!)];
|
|
16
|
+
let indent = '';
|
|
17
|
+
for (let i = 1; i < chain.length; i++) {
|
|
18
|
+
lines.push(`${indent}└── ${fmtNode(chain[i]!)}`);
|
|
19
|
+
indent += ' ';
|
|
20
|
+
}
|
|
21
|
+
return lines.join('\n');
|
|
22
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { appendFileSync } from "node:fs";
|
|
2
|
+
import { Effect, Layer, pipe } from "effect";
|
|
3
|
+
import { OSPlatformImpl } from "@/modules/common/os-platform/impl";
|
|
4
|
+
import { CryptoImpl } from "@/modules/common/crypto/impl";
|
|
5
|
+
import { KeyMapStoreImpl } from "@/modules/common/kep-map-store/impl";
|
|
6
|
+
import { SSHConfigImpl } from "@/modules/common/ssh-config/impl";
|
|
7
|
+
import { SSHAgentImpl } from "@/modules/ssh-agent/ssh-agent/impl";
|
|
8
|
+
import { SessionImpl } from "@/modules/ssh-agent/session/impl";
|
|
9
|
+
import { AGENT_LOG_FILE_PATH, AGENT_PID_FILE_PATH, AGENT_PORT_FILE_PATH, AGENT_SOCK_FILE_PATH } from "@/common/constants";
|
|
10
|
+
import { rmSync } from "node:fs";
|
|
11
|
+
|
|
12
|
+
const origLog = console.log.bind(console);
|
|
13
|
+
const origError = console.error.bind(console);
|
|
14
|
+
const origWarn = console.warn.bind(console);
|
|
15
|
+
|
|
16
|
+
function logToFile(...args: any[]) {
|
|
17
|
+
try {
|
|
18
|
+
appendFileSync(AGENT_LOG_FILE_PATH, args.map(arg => {
|
|
19
|
+
try { return JSON.stringify(arg) }
|
|
20
|
+
catch(e) { return String(e); }
|
|
21
|
+
}).join(' ') + '\n');
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log = (...args: any[]) => { logToFile(...args); origLog(...args); };
|
|
26
|
+
console.error = (...args: any[]) => { logToFile(...args); origError(...args); };
|
|
27
|
+
console.warn = (...args: any[]) => { logToFile(...args); origWarn(...args); };
|
|
28
|
+
|
|
29
|
+
function cleanup() {
|
|
30
|
+
try { rmSync(AGENT_PID_FILE_PATH); } catch {}
|
|
31
|
+
try { rmSync(AGENT_PORT_FILE_PATH); } catch {}
|
|
32
|
+
try { rmSync(AGENT_SOCK_FILE_PATH); } catch {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
process.on("exit", cleanup);
|
|
36
|
+
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
37
|
+
process.on("SIGINT", () => { cleanup(); process.exit(0); });
|
|
38
|
+
|
|
39
|
+
pipe(
|
|
40
|
+
SSHAgentImpl.ServiceLayer,
|
|
41
|
+
Layer.provideMerge(SessionImpl.makeRpcLayer()),
|
|
42
|
+
Layer.provideMerge(KeyMapStoreImpl.Layer),
|
|
43
|
+
Layer.provideMerge(SSHConfigImpl.Layer),
|
|
44
|
+
Layer.provideMerge(CryptoImpl.Layer),
|
|
45
|
+
Layer.provideMerge(OSPlatformImpl.Layer),
|
|
46
|
+
Layer.launch,
|
|
47
|
+
Effect.runPromise
|
|
48
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AGENT_SOCK_FILE_PATH } from "@/common/constants";
|
|
2
|
+
|
|
3
|
+
const result = Bun.spawnSync(["ssh-keygen", ...Bun.argv.slice(2)], {
|
|
4
|
+
stdin: "inherit",
|
|
5
|
+
stdout: "inherit",
|
|
6
|
+
stderr: "inherit",
|
|
7
|
+
env: { ...process.env, SSH_AUTH_SOCK: AGENT_SOCK_FILE_PATH },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
process.exit(result.exitCode ?? 1);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { appendFileSync } from "node:fs";
|
|
3
|
+
import { commonModules } from "@/modules/common";
|
|
4
|
+
import { OSPlatformImpl } from "@/modules/common/os-platform/impl";
|
|
5
|
+
import { VIRTUAL_KEY_LOG_FILE_PATH } from "@/common/constants";
|
|
6
|
+
|
|
7
|
+
const program = Effect.gen(function* () {
|
|
8
|
+
const platform = yield* commonModules.OSPlatform;
|
|
9
|
+
yield* platform.registerVirtualHID((data) =>
|
|
10
|
+
Effect.sync(() => {
|
|
11
|
+
appendFileSync(VIRTUAL_KEY_LOG_FILE_PATH, `[vhid] received ${data.length} bytes: ${data.toString('hex')}\n`);
|
|
12
|
+
return Buffer.alloc(64);
|
|
13
|
+
})
|
|
14
|
+
);
|
|
15
|
+
yield* Effect.never;
|
|
16
|
+
}).pipe(Effect.provide(OSPlatformImpl.Layer));
|
|
17
|
+
|
|
18
|
+
Effect.runPromise(program).catch(e => {
|
|
19
|
+
console.error('Virtual security key failed:', e);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|