@ozyman42/ozy-cli 0.4.9-windows-x64.0 → 0.4.9
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/LICENSE +21 -0
- package/README.md +8 -0
- package/bin/ozy +209 -0
- package/bin/ozy-signing-agent +209 -0
- package/bin/ozy-ssh-keygen +209 -0
- package/bin/ozy-virtual-security-key +209 -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 +236 -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 +95 -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 +444 -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/constants.js +12 -0
- package/dist/scripts/constants.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 +83 -0
- package/dist/scripts/publish.js.map +1 -0
- package/install.js +209 -0
- package/package.json +58 -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 +261 -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 +91 -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 +559 -0
- package/src/scripts/check-npm-version.ts +7 -0
- package/src/scripts/command-launcher.js.ejs +186 -0
- package/src/scripts/constants.ts +11 -0
- package/src/scripts/kill-existing-agent.ts +22 -0
- package/src/scripts/publish.ts +89 -0
- package/src/scripts/windows-bin-shims.md +1104 -0
- package/multi-call-binary.exe +0 -0
|
@@ -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,261 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { Effect, Option } from "effect";
|
|
4
|
+
import { makeCommand } from "@/common/command";
|
|
5
|
+
import { CLI_CMD_NAME, CLI_CMD_PATH, PACKAGE_NAME } from "@/common/constants";
|
|
6
|
+
|
|
7
|
+
type PackageManager = "bun" | "pnpm" | "npm" | "yarn";
|
|
8
|
+
type InstallScope = "global" | "local";
|
|
9
|
+
|
|
10
|
+
interface InstallInfo {
|
|
11
|
+
pm: PackageManager;
|
|
12
|
+
scope: InstallScope;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const PACKAGE_NODE_MODULES_SEGMENT = `/node_modules/${PACKAGE_NAME}/`;
|
|
17
|
+
const LOCAL_BIN_SEGMENT = "/node_modules/.bin/";
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* Directory shapes this detector understands.
|
|
21
|
+
*
|
|
22
|
+
* Local installs:
|
|
23
|
+
* project/
|
|
24
|
+
* node_modules/
|
|
25
|
+
* .bin/
|
|
26
|
+
* ozy -> ../@ozyman42/ozy-cli/bin/ozy # POSIX link or shell shim
|
|
27
|
+
* ozy.cmd / ozy.ps1 / ozy.exe # Windows manager-specific shim
|
|
28
|
+
* @ozyman42/
|
|
29
|
+
* ozy-cli/
|
|
30
|
+
* bin/
|
|
31
|
+
* ozy # CLI_CMD_PATH after repair
|
|
32
|
+
*
|
|
33
|
+
* Bun global:
|
|
34
|
+
* ~/.bun/bin/ozy -> ~/.bun/install/global/node_modules/@ozyman42/ozy-cli/bin/ozy
|
|
35
|
+
*
|
|
36
|
+
* pnpm global:
|
|
37
|
+
* ~/Library/pnpm/ozy -> ~/.local/share/pnpm/global/<slot>/node_modules/@ozyman42/ozy-cli/bin/ozy
|
|
38
|
+
* ~/.pnpm-global/.../node_modules/@ozyman42/ozy-cli/bin/ozy
|
|
39
|
+
*
|
|
40
|
+
* npm global:
|
|
41
|
+
* ~/.nvm/versions/node/vX.Y.Z/bin/ozy -> ~/.nvm/versions/node/vX.Y.Z/lib/node_modules/@ozyman42/ozy-cli/bin/ozy
|
|
42
|
+
* /usr/local/bin/ozy -> /usr/local/lib/node_modules/@ozyman42/ozy-cli/bin/ozy
|
|
43
|
+
* %APPDATA%/npm/ozy.cmd -> %APPDATA%/npm/node_modules/@ozyman42/ozy-cli/bin/ozy
|
|
44
|
+
*
|
|
45
|
+
* Yarn Classic global:
|
|
46
|
+
* ~/.yarn/bin/ozy -> ~/.config/yarn/global/node_modules/@ozyman42/ozy-cli/bin/ozy
|
|
47
|
+
* %LOCALAPPDATA%/Yarn/bin/ozy.cmd -> %LOCALAPPDATA%/Yarn/Data/global/node_modules/@ozyman42/ozy-cli/bin/ozy
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
function normalizePath(filePath: string): string {
|
|
51
|
+
return filePath.replace(/\\/g, "/");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parentDirectory(dir: string): string {
|
|
55
|
+
return normalizePath(path.dirname(dir));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hasProjectMarkers(dir: string): boolean {
|
|
59
|
+
return (
|
|
60
|
+
existsSync(path.join(dir, "package.json")) ||
|
|
61
|
+
existsSync(path.join(dir, "bun.lock")) ||
|
|
62
|
+
existsSync(path.join(dir, "bun.lockb")) ||
|
|
63
|
+
existsSync(path.join(dir, "pnpm-lock.yaml")) ||
|
|
64
|
+
existsSync(path.join(dir, "yarn.lock")) ||
|
|
65
|
+
existsSync(path.join(dir, "package-lock.json"))
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function packageManagerName(value: unknown): Option.Option<PackageManager> {
|
|
70
|
+
if (typeof value !== "string") return Option.none();
|
|
71
|
+
if (value.startsWith("bun@") || value === "bun") return Option.some("bun");
|
|
72
|
+
if (value.startsWith("pnpm@") || value === "pnpm") return Option.some("pnpm");
|
|
73
|
+
if (value.startsWith("yarn@") || value === "yarn") return Option.some("yarn");
|
|
74
|
+
if (value.startsWith("npm@") || value === "npm") return Option.some("npm");
|
|
75
|
+
return Option.none();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function packageManagerFromPackageJson(dir: string): Option.Option<PackageManager> {
|
|
79
|
+
const packageJsonPath = path.join(dir, "package.json");
|
|
80
|
+
if (!existsSync(packageJsonPath)) return Option.none();
|
|
81
|
+
try {
|
|
82
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
83
|
+
const packageManager = packageManagerName(packageJson.packageManager);
|
|
84
|
+
if (Option.isSome(packageManager)) return packageManager;
|
|
85
|
+
|
|
86
|
+
const devEnginePackageManager = packageJson.devEngines?.packageManager;
|
|
87
|
+
if (Array.isArray(devEnginePackageManager)) {
|
|
88
|
+
for (const entry of devEnginePackageManager) {
|
|
89
|
+
const pm = packageManagerName(entry?.name);
|
|
90
|
+
if (Option.isSome(pm)) return pm;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return packageManagerName(devEnginePackageManager?.name);
|
|
95
|
+
} catch {
|
|
96
|
+
return Option.none();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function findProjectPackageManager(dir: string): Option.Option<PackageManager> {
|
|
101
|
+
let current = dir;
|
|
102
|
+
while (true) {
|
|
103
|
+
const packageManager = packageManagerFromPackageJson(current);
|
|
104
|
+
if (Option.isSome(packageManager)) return packageManager;
|
|
105
|
+
if (existsSync(path.join(current, "bun.lock")) || existsSync(path.join(current, "bun.lockb"))) return Option.some("bun");
|
|
106
|
+
if (existsSync(path.join(current, "pnpm-lock.yaml"))) return Option.some("pnpm");
|
|
107
|
+
if (existsSync(path.join(current, "yarn.lock"))) return Option.some("yarn");
|
|
108
|
+
if (existsSync(path.join(current, "package-lock.json"))) return Option.some("npm");
|
|
109
|
+
const parent = parentDirectory(current);
|
|
110
|
+
if (parent === current) return Option.none();
|
|
111
|
+
current = parent;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function detectGlobalInstall(normalizedPath: string): Option.Option<InstallInfo> {
|
|
116
|
+
const lowerPath = normalizedPath.toLowerCase();
|
|
117
|
+
|
|
118
|
+
if (lowerPath.includes("/.bun/bin/") || lowerPath.includes("/.bun/install/global/node_modules/"))
|
|
119
|
+
return Option.some({ pm: "bun", scope: "global" });
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
lowerPath.includes("/.pnpm-global/") ||
|
|
123
|
+
lowerPath.includes("/pnpm/global/") ||
|
|
124
|
+
lowerPath.includes("/library/pnpm/") ||
|
|
125
|
+
lowerPath.includes("/appdata/local/pnpm/")
|
|
126
|
+
)
|
|
127
|
+
return Option.some({ pm: "pnpm", scope: "global" });
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
lowerPath.includes("/.yarn/bin/") ||
|
|
131
|
+
lowerPath.includes("/appdata/local/yarn/bin/") ||
|
|
132
|
+
lowerPath.includes("/.config/yarn/global/node_modules/") ||
|
|
133
|
+
(lowerPath.includes("/appdata/local/yarn/") && lowerPath.includes("/global/node_modules/"))
|
|
134
|
+
)
|
|
135
|
+
return Option.some({ pm: "yarn", scope: "global" });
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
lowerPath.includes("/.nvm/versions/node/") ||
|
|
139
|
+
lowerPath.includes("/.nodenv/versions/") ||
|
|
140
|
+
// asdf is a multi-runtime version manager; default data lives under ~/.asdf.
|
|
141
|
+
// https://asdf-vm.com/guide/getting-started.html
|
|
142
|
+
lowerPath.includes("/.asdf/installs/nodejs/") ||
|
|
143
|
+
lowerPath.includes("/appdata/roaming/npm/") ||
|
|
144
|
+
lowerPath.includes("/program files/nodejs/node_modules/") ||
|
|
145
|
+
lowerPath.startsWith("/usr/local/bin/") ||
|
|
146
|
+
lowerPath.startsWith("/opt/homebrew/bin/") ||
|
|
147
|
+
lowerPath.startsWith("/usr/local/lib/node_modules/") ||
|
|
148
|
+
lowerPath.startsWith("/opt/homebrew/lib/node_modules/") ||
|
|
149
|
+
lowerPath.startsWith("/usr/lib/node_modules/")
|
|
150
|
+
)
|
|
151
|
+
return Option.some({ pm: "npm", scope: "global" });
|
|
152
|
+
|
|
153
|
+
return Option.none();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function localInstallInfo(projectRoot: string): Option.Option<InstallInfo> {
|
|
157
|
+
if (!hasProjectMarkers(projectRoot)) return Option.none();
|
|
158
|
+
const pm = Option.getOrElse(findProjectPackageManager(projectRoot), () => "npm" as PackageManager);
|
|
159
|
+
return Option.some({ pm, scope: "local", cwd: projectRoot });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function detectLocalInstall(normalizedPath: string): Option.Option<InstallInfo> {
|
|
163
|
+
const lowerPath = normalizedPath.toLowerCase();
|
|
164
|
+
|
|
165
|
+
const localBinIndex = lowerPath.indexOf(LOCAL_BIN_SEGMENT);
|
|
166
|
+
if (localBinIndex >= 0) {
|
|
167
|
+
return localInstallInfo(normalizedPath.slice(0, localBinIndex));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const packageIndex = lowerPath.indexOf(PACKAGE_NODE_MODULES_SEGMENT);
|
|
171
|
+
if (packageIndex >= 0) {
|
|
172
|
+
return localInstallInfo(normalizedPath.slice(0, packageIndex));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return Option.none();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function detectInstall(binaryPath: string): Option.Option<InstallInfo> {
|
|
179
|
+
const normalizedPath = normalizePath(binaryPath);
|
|
180
|
+
const globalInstall = detectGlobalInstall(normalizedPath);
|
|
181
|
+
if (Option.isSome(globalInstall)) return globalInstall;
|
|
182
|
+
return detectLocalInstall(normalizedPath);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function buildUpgradeCommand(info: InstallInfo): string[] {
|
|
186
|
+
const pkg = `${PACKAGE_NAME}@latest`;
|
|
187
|
+
if (info.scope === "global") {
|
|
188
|
+
if (info.pm === "bun") return ["bun", "add", "--no-cache", "-g", pkg];
|
|
189
|
+
if (info.pm === "pnpm") return ["pnpm", "add", "-g", pkg];
|
|
190
|
+
if (info.pm === "yarn") return ["yarn", "global", "add", pkg];
|
|
191
|
+
return ["npm", "install", "-g", pkg];
|
|
192
|
+
}
|
|
193
|
+
if (info.pm === "bun") return ["bun", "add", "--no-cache", pkg];
|
|
194
|
+
if (info.pm === "pnpm") return ["pnpm", "add", pkg];
|
|
195
|
+
if (info.pm === "yarn") return ["yarn", "add", pkg];
|
|
196
|
+
return ["npm", "install", pkg];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function quotePowerShellString(value: string): string {
|
|
200
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function renderWindowsUpdaterCommand(cmd: string[], cwd: string, parentPid: number): string {
|
|
204
|
+
const command = cmd.map(quotePowerShellString).join(", ");
|
|
205
|
+
const commandDisplay = cmd.join(" ");
|
|
206
|
+
return [
|
|
207
|
+
"$ErrorActionPreference = 'Stop'",
|
|
208
|
+
"Write-Host 'Waiting for the current process to exit before upgrading...'",
|
|
209
|
+
`while (Get-Process -Id ${parentPid} -ErrorAction SilentlyContinue) { Start-Sleep -Milliseconds 250 }`,
|
|
210
|
+
`Set-Location -LiteralPath ${quotePowerShellString(cwd)}`,
|
|
211
|
+
`Write-Host ${quotePowerShellString(`Running: ${commandDisplay}`)}`,
|
|
212
|
+
`$command = @(${command})`,
|
|
213
|
+
"$executable = $command[0]",
|
|
214
|
+
"$arguments = if ($command.Length -gt 1) { $command[1..($command.Length - 1)] } else { @() }",
|
|
215
|
+
"& $executable @arguments",
|
|
216
|
+
"exit $LASTEXITCODE",
|
|
217
|
+
].join("; ");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function spawnWindowsUpdater(cmd: string[], cwd: string): void {
|
|
221
|
+
const updater = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", renderWindowsUpdaterCommand(cmd, cwd, process.pid)];
|
|
222
|
+
console.log("Spawning windows updater...");
|
|
223
|
+
const proc = Bun.spawn(updater, {
|
|
224
|
+
cwd,
|
|
225
|
+
stdin: "ignore",
|
|
226
|
+
stdout: "inherit",
|
|
227
|
+
stderr: "inherit",
|
|
228
|
+
});
|
|
229
|
+
proc.unref();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const upgrade = makeCommand("upgrade", `upgrade ${CLI_CMD_NAME} to the latest version`, () =>
|
|
233
|
+
Effect.gen(function* () {
|
|
234
|
+
const binaryPath = CLI_CMD_PATH;
|
|
235
|
+
const infoOption = detectInstall(binaryPath);
|
|
236
|
+
if (Option.isNone(infoOption))
|
|
237
|
+
return yield* Effect.fail(`unknown-install: could not detect package manager from binary path: ${binaryPath}\nUpgrade manually with your package manager.`);
|
|
238
|
+
const info = infoOption.value;
|
|
239
|
+
|
|
240
|
+
const cmd = buildUpgradeCommand(info);
|
|
241
|
+
console.log(`Detected that ${PACKAGE_NAME} was installed ${info.scope}ly using ${info.pm}.`);
|
|
242
|
+
console.log(`Running: ${cmd.join(" ")}`);
|
|
243
|
+
|
|
244
|
+
// Windows locks the running package-owned .exe, so asking the package manager to replace
|
|
245
|
+
// this package in-process can fail with EBUSY. A helper process waits for this process to
|
|
246
|
+
// exit, then runs the same install command outside the executable tree being replaced.
|
|
247
|
+
if (process.platform === "win32") {
|
|
248
|
+
spawnWindowsUpdater(cmd, info.cwd ?? process.cwd());
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const proc = Bun.spawn(cmd, {
|
|
253
|
+
cwd: info.cwd ?? process.cwd(),
|
|
254
|
+
stdout: "inherit",
|
|
255
|
+
stderr: "inherit",
|
|
256
|
+
});
|
|
257
|
+
const code = yield* Effect.promise(() => proc.exited);
|
|
258
|
+
if (code !== 0)
|
|
259
|
+
yield* Effect.fail(`install-failed: ${cmd[0]} exited with code ${code}`);
|
|
260
|
+
})
|
|
261
|
+
);
|
|
@@ -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 {};
|