@ozyman42/ozy-cli 0.4.9-linux-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.
Files changed (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -0
  3. package/bin/ozy +209 -0
  4. package/bin/ozy-signing-agent +209 -0
  5. package/bin/ozy-ssh-keygen +209 -0
  6. package/bin/ozy-virtual-security-key +209 -0
  7. package/dist/commands/git/hosts.js +18 -0
  8. package/dist/commands/git/hosts.js.map +1 -0
  9. package/dist/commands/git/index.js +8 -0
  10. package/dist/commands/git/index.js.map +1 -0
  11. package/dist/commands/git/setup.js +145 -0
  12. package/dist/commands/git/setup.js.map +1 -0
  13. package/dist/commands/index.js +13 -0
  14. package/dist/commands/index.js.map +1 -0
  15. package/dist/commands/npm/index.js +7 -0
  16. package/dist/commands/npm/index.js.map +1 -0
  17. package/dist/commands/npm/setup/git-remote.js +66 -0
  18. package/dist/commands/npm/setup/git-remote.js.map +1 -0
  19. package/dist/commands/npm/setup/index.js +168 -0
  20. package/dist/commands/npm/setup/index.js.map +1 -0
  21. package/dist/commands/npm/setup/npm-auth.js +62 -0
  22. package/dist/commands/npm/setup/npm-auth.js.map +1 -0
  23. package/dist/commands/npm/setup/package-json-validate.js +47 -0
  24. package/dist/commands/npm/setup/package-json-validate.js.map +1 -0
  25. package/dist/commands/npm/setup/package-registry.js +20 -0
  26. package/dist/commands/npm/setup/package-registry.js.map +1 -0
  27. package/dist/commands/npm/setup/setup.test.js +95 -0
  28. package/dist/commands/npm/setup/setup.test.js.map +1 -0
  29. package/dist/commands/npm/setup/trusted-publishing.js +84 -0
  30. package/dist/commands/npm/setup/trusted-publishing.js.map +1 -0
  31. package/dist/commands/npm/setup/workflow.js +19 -0
  32. package/dist/commands/npm/setup/workflow.js.map +1 -0
  33. package/dist/commands/ssh/get-sk-credential.js +104 -0
  34. package/dist/commands/ssh/get-sk-credential.js.map +1 -0
  35. package/dist/commands/ssh/index.js +7 -0
  36. package/dist/commands/ssh/index.js.map +1 -0
  37. package/dist/commands/upgrade.js +236 -0
  38. package/dist/commands/upgrade.js.map +1 -0
  39. package/dist/common/command.js +15 -0
  40. package/dist/common/command.js.map +1 -0
  41. package/dist/common/constants.js +25 -0
  42. package/dist/common/constants.js.map +1 -0
  43. package/dist/common/effective-modules-extensions.js +13 -0
  44. package/dist/common/effective-modules-extensions.js.map +1 -0
  45. package/dist/common/log.js +4 -0
  46. package/dist/common/log.js.map +1 -0
  47. package/dist/common/render-caller-tree.js +20 -0
  48. package/dist/common/render-caller-tree.js.map +1 -0
  49. package/dist/entrypoints/ozy-signing-agent.js +48 -0
  50. package/dist/entrypoints/ozy-signing-agent.js.map +1 -0
  51. package/dist/entrypoints/ozy-ssh-keygen.js +9 -0
  52. package/dist/entrypoints/ozy-ssh-keygen.js.map +1 -0
  53. package/dist/entrypoints/ozy-virtual-security-key.js +18 -0
  54. package/dist/entrypoints/ozy-virtual-security-key.js.map +1 -0
  55. package/dist/entrypoints/ozy.js +3 -0
  56. package/dist/entrypoints/ozy.js.map +1 -0
  57. package/dist/index.js +2 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/internal/thing.js +4 -0
  60. package/dist/internal/thing.js.map +1 -0
  61. package/dist/modules/cli/agent-client/impl.js +109 -0
  62. package/dist/modules/cli/agent-client/impl.js.map +1 -0
  63. package/dist/modules/cli/agent-client/interface.js +11 -0
  64. package/dist/modules/cli/agent-client/interface.js.map +1 -0
  65. package/dist/modules/cli/git/impl.js +124 -0
  66. package/dist/modules/cli/git/impl.js.map +1 -0
  67. package/dist/modules/cli/git/interface.js +3 -0
  68. package/dist/modules/cli/git/interface.js.map +1 -0
  69. package/dist/modules/cli/git/submodule/fix.js +257 -0
  70. package/dist/modules/cli/git/submodule/fix.js.map +1 -0
  71. package/dist/modules/cli/git/submodule/git-state.js +154 -0
  72. package/dist/modules/cli/git/submodule/git-state.js.map +1 -0
  73. package/dist/modules/cli/git/submodule/gitmodules.js +113 -0
  74. package/dist/modules/cli/git/submodule/gitmodules.js.map +1 -0
  75. package/dist/modules/cli/git/submodule/printer.js +232 -0
  76. package/dist/modules/cli/git/submodule/printer.js.map +1 -0
  77. package/dist/modules/cli/git/submodule/resolve-git-dir.js +16 -0
  78. package/dist/modules/cli/git/submodule/resolve-git-dir.js.map +1 -0
  79. package/dist/modules/cli/github/impl.js +137 -0
  80. package/dist/modules/cli/github/impl.js.map +1 -0
  81. package/dist/modules/cli/github/interface.js +2 -0
  82. package/dist/modules/cli/github/interface.js.map +1 -0
  83. package/dist/modules/cli/index.js +9 -0
  84. package/dist/modules/cli/index.js.map +1 -0
  85. package/dist/modules/common/crypto/impl.js +133 -0
  86. package/dist/modules/common/crypto/impl.js.map +1 -0
  87. package/dist/modules/common/crypto/interface.js +2 -0
  88. package/dist/modules/common/crypto/interface.js.map +1 -0
  89. package/dist/modules/common/index.js +10 -0
  90. package/dist/modules/common/index.js.map +1 -0
  91. package/dist/modules/common/kep-map-store/impl.js +42 -0
  92. package/dist/modules/common/kep-map-store/impl.js.map +1 -0
  93. package/dist/modules/common/kep-map-store/interface.js +2 -0
  94. package/dist/modules/common/kep-map-store/interface.js.map +1 -0
  95. package/dist/modules/common/os-platform/caller-info.js +132 -0
  96. package/dist/modules/common/os-platform/caller-info.js.map +1 -0
  97. package/dist/modules/common/os-platform/impl.js +95 -0
  98. package/dist/modules/common/os-platform/impl.js.map +1 -0
  99. package/dist/modules/common/os-platform/interface.js +2 -0
  100. package/dist/modules/common/os-platform/interface.js.map +1 -0
  101. package/dist/modules/common/os-platform/virtual-hid/index.js +12 -0
  102. package/dist/modules/common/os-platform/virtual-hid/index.js.map +1 -0
  103. package/dist/modules/common/os-platform/virtual-hid/linux.js +5 -0
  104. package/dist/modules/common/os-platform/virtual-hid/linux.js.map +1 -0
  105. package/dist/modules/common/os-platform/virtual-hid/mac.js +119 -0
  106. package/dist/modules/common/os-platform/virtual-hid/mac.js.map +1 -0
  107. package/dist/modules/common/os-platform/virtual-hid/windows.js +5 -0
  108. package/dist/modules/common/os-platform/virtual-hid/windows.js.map +1 -0
  109. package/dist/modules/common/ssh-config/impl.js +116 -0
  110. package/dist/modules/common/ssh-config/impl.js.map +1 -0
  111. package/dist/modules/common/ssh-config/interface.js +10 -0
  112. package/dist/modules/common/ssh-config/interface.js.map +1 -0
  113. package/dist/modules/ssh-agent/index.js +8 -0
  114. package/dist/modules/ssh-agent/index.js.map +1 -0
  115. package/dist/modules/ssh-agent/session/impl.js +265 -0
  116. package/dist/modules/ssh-agent/session/impl.js.map +1 -0
  117. package/dist/modules/ssh-agent/session/interface-rpc.js +20 -0
  118. package/dist/modules/ssh-agent/session/interface-rpc.js.map +1 -0
  119. package/dist/modules/ssh-agent/session/interface.js +32 -0
  120. package/dist/modules/ssh-agent/session/interface.js.map +1 -0
  121. package/dist/modules/ssh-agent/session/passkey-prf-page.js +204 -0
  122. package/dist/modules/ssh-agent/session/passkey-prf-page.js.map +1 -0
  123. package/dist/modules/ssh-agent/session/prf-flow.js +103 -0
  124. package/dist/modules/ssh-agent/session/prf-flow.js.map +1 -0
  125. package/dist/modules/ssh-agent/ssh-agent/impl.js +117 -0
  126. package/dist/modules/ssh-agent/ssh-agent/impl.js.map +1 -0
  127. package/dist/modules/ssh-agent/ssh-agent/interface.js +2 -0
  128. package/dist/modules/ssh-agent/ssh-agent/interface.js.map +1 -0
  129. package/dist/scripts/build.js +444 -0
  130. package/dist/scripts/build.js.map +1 -0
  131. package/dist/scripts/check-npm-version.js +8 -0
  132. package/dist/scripts/check-npm-version.js.map +1 -0
  133. package/dist/scripts/constants.js +12 -0
  134. package/dist/scripts/constants.js.map +1 -0
  135. package/dist/scripts/kill-existing-agent.js +21 -0
  136. package/dist/scripts/kill-existing-agent.js.map +1 -0
  137. package/dist/scripts/publish.js +83 -0
  138. package/dist/scripts/publish.js.map +1 -0
  139. package/install.js +209 -0
  140. package/package.json +58 -11
  141. package/src/commands/git/hosts.ts +20 -0
  142. package/src/commands/git/index.ts +9 -0
  143. package/src/commands/git/setup.ts +182 -0
  144. package/src/commands/index.ts +15 -0
  145. package/src/commands/npm/index.ts +8 -0
  146. package/src/commands/npm/setup/git-remote.ts +78 -0
  147. package/src/commands/npm/setup/index.ts +189 -0
  148. package/src/commands/npm/setup/npm-auth.ts +58 -0
  149. package/src/commands/npm/setup/package-json-validate.ts +68 -0
  150. package/src/commands/npm/setup/package-registry.ts +24 -0
  151. package/src/commands/npm/setup/publish-workflow.yml +35 -0
  152. package/src/commands/npm/setup/setup.test.ts +117 -0
  153. package/src/commands/npm/setup/trusted-publishing.ts +106 -0
  154. package/src/commands/npm/setup/workflow.ts +20 -0
  155. package/src/commands/ssh/get-sk-credential.ts +110 -0
  156. package/src/commands/ssh/index.ts +8 -0
  157. package/src/commands/upgrade.ts +261 -0
  158. package/src/common/command.ts +16 -0
  159. package/src/common/constants.ts +26 -0
  160. package/src/common/effective-modules-extensions.ts +24 -0
  161. package/src/common/log.ts +3 -0
  162. package/src/common/render-caller-tree.ts +22 -0
  163. package/src/entrypoints/ozy-signing-agent.ts +48 -0
  164. package/src/entrypoints/ozy-ssh-keygen.ts +10 -0
  165. package/src/entrypoints/ozy-virtual-security-key.ts +21 -0
  166. package/src/entrypoints/ozy.ts +2 -0
  167. package/src/index.ts +1 -0
  168. package/src/internal/thing.ts +3 -0
  169. package/src/modules/cli/agent-client/impl.ts +152 -0
  170. package/src/modules/cli/agent-client/interface.ts +17 -0
  171. package/src/modules/cli/git/impl.ts +133 -0
  172. package/src/modules/cli/git/interface.ts +23 -0
  173. package/src/modules/cli/git/submodule/fix.ts +298 -0
  174. package/src/modules/cli/git/submodule/git-state.ts +217 -0
  175. package/src/modules/cli/git/submodule/gitmodules.ts +126 -0
  176. package/src/modules/cli/git/submodule/printer.ts +256 -0
  177. package/src/modules/cli/git/submodule/resolve-git-dir.ts +14 -0
  178. package/src/modules/cli/github/impl.ts +169 -0
  179. package/src/modules/cli/github/interface.ts +29 -0
  180. package/src/modules/cli/index.ts +16 -0
  181. package/src/modules/common/crypto/impl.ts +173 -0
  182. package/src/modules/common/crypto/interface.ts +20 -0
  183. package/src/modules/common/index.ts +19 -0
  184. package/src/modules/common/kep-map-store/impl.ts +47 -0
  185. package/src/modules/common/kep-map-store/interface.ts +9 -0
  186. package/src/modules/common/os-platform/caller-info.ts +152 -0
  187. package/src/modules/common/os-platform/impl.ts +91 -0
  188. package/src/modules/common/os-platform/interface.ts +16 -0
  189. package/src/modules/common/os-platform/virtual-hid/index.ts +12 -0
  190. package/src/modules/common/os-platform/virtual-hid/linux.ts +7 -0
  191. package/src/modules/common/os-platform/virtual-hid/mac.ts +150 -0
  192. package/src/modules/common/os-platform/virtual-hid/windows.ts +7 -0
  193. package/src/modules/common/ssh-config/impl.ts +130 -0
  194. package/src/modules/common/ssh-config/interface.ts +33 -0
  195. package/src/modules/ssh-agent/index.ts +13 -0
  196. package/src/modules/ssh-agent/session/agent-notes.md +210 -0
  197. package/src/modules/ssh-agent/session/design.md +2 -0
  198. package/src/modules/ssh-agent/session/html-refactor.md +23 -0
  199. package/src/modules/ssh-agent/session/impl.ts +328 -0
  200. package/src/modules/ssh-agent/session/interface-rpc.ts +24 -0
  201. package/src/modules/ssh-agent/session/interface.ts +62 -0
  202. package/src/modules/ssh-agent/session/passkey-prf-page.ts +224 -0
  203. package/src/modules/ssh-agent/session/prf-flow.ts +152 -0
  204. package/src/modules/ssh-agent/ssh-agent/impl.ts +136 -0
  205. package/src/modules/ssh-agent/ssh-agent/interface.ts +9 -0
  206. package/src/scripts/build.ts +559 -0
  207. package/src/scripts/check-npm-version.ts +7 -0
  208. package/src/scripts/command-launcher.js.ejs +186 -0
  209. package/src/scripts/constants.ts +11 -0
  210. package/src/scripts/kill-existing-agent.ts +22 -0
  211. package/src/scripts/publish.ts +89 -0
  212. package/src/scripts/windows-bin-shims.md +1104 -0
  213. package/multi-call-binary +0 -0
package/install.js ADDED
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { spawnSync } from "node:child_process";
4
+ import {
5
+ chmodSync,
6
+ copyFileSync,
7
+ existsSync,
8
+ linkSync,
9
+ mkdirSync,
10
+ readFileSync,
11
+ renameSync,
12
+ rmSync,
13
+ writeFileSync,
14
+ } from "node:fs";
15
+ import { basename, dirname, join } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+
18
+ const PLATFORM_MAP = {
19
+ "darwin-arm64": "darwin-arm64",
20
+ "darwin-x64": "darwin-x64",
21
+ "linux-arm64": "linux-arm64",
22
+ "linux-x64": "linux-x64",
23
+ "win32-x64": "windows-x64"
24
+ };
25
+ const PLATFORM_PACKAGES = {
26
+ "darwin-arm64": "@ozyman42/ozy-cli-darwin-arm64",
27
+ "darwin-x64": "@ozyman42/ozy-cli-darwin-x64",
28
+ "linux-arm64": "@ozyman42/ozy-cli-linux-arm64",
29
+ "linux-x64": "@ozyman42/ozy-cli-linux-x64",
30
+ "windows-x64": "@ozyman42/ozy-cli-windows-x64"
31
+ };
32
+ const PLATFORM_BINARIES = {
33
+ "darwin-arm64": "multi-call-binary",
34
+ "darwin-x64": "multi-call-binary",
35
+ "linux-arm64": "multi-call-binary",
36
+ "linux-x64": "multi-call-binary",
37
+ "windows-x64": "multi-call-binary.exe"
38
+ };
39
+ const COMMANDS = [
40
+ "ozy",
41
+ "ozy-signing-agent",
42
+ "ozy-ssh-keygen",
43
+ "ozy-virtual-security-key"
44
+ ];
45
+ const BIN_DIR = "bin";
46
+ const INSTALL_SCRIPT = "install.js";
47
+ // Root install.js repairs command targets; bin launchers repair and then execute.
48
+ const IS_INSTALL_SCRIPT = true;
49
+ const WINDOWS_EXECUTABLE_SUFFIX = ".exe";
50
+ const INSTALLED_JSON = "installed.json";
51
+
52
+ const require = createRequire(import.meta.url);
53
+ const launcherPath = fileURLToPath(import.meta.url);
54
+ const launcherDir = dirname(launcherPath);
55
+ const packageDir = IS_INSTALL_SCRIPT ? launcherDir : dirname(launcherDir);
56
+ const commandTargetDir = join(packageDir, BIN_DIR);
57
+ const rootInstallScriptPath = join(packageDir, INSTALL_SCRIPT);
58
+
59
+ function fail(message) {
60
+ console.error(message);
61
+ process.exit(1);
62
+ }
63
+
64
+ function currentPlatformKey() {
65
+ const platformKey = PLATFORM_MAP[`${process.platform}-${process.arch}`];
66
+ if (!platformKey) {
67
+ fail(`Unsupported platform: ${process.platform}-${process.arch}`);
68
+ }
69
+ return platformKey;
70
+ }
71
+
72
+ function currentPlatformPackageName() {
73
+ const platformKey = currentPlatformKey();
74
+ const packageName = PLATFORM_PACKAGES[platformKey];
75
+ if (!packageName) {
76
+ fail(`No platform package is configured for ${platformKey}`);
77
+ }
78
+ return packageName;
79
+ }
80
+
81
+ /**
82
+ * When a Jive library contains any number of CLIs (entrypoints or agents), then the base package
83
+ * will have an optional dependency for each possible architecture, with those platform-specific
84
+ * packages containing a platform build of the CLI.
85
+ * @returns the path to the platform binary and the exact version of the platform package.
86
+ */
87
+ function currentPlatformPackage() {
88
+ const platformKey = currentPlatformKey();
89
+ const binaryFile = PLATFORM_BINARIES[platformKey];
90
+ if (!binaryFile) {
91
+ fail(`No platform binary is configured for ${platformKey}`);
92
+ }
93
+
94
+ const packageName = currentPlatformPackageName();
95
+ try {
96
+ const packageJsonPath = require.resolve(`${packageName}/package.json`);
97
+ const packageDir = dirname(packageJsonPath);
98
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
99
+ return {
100
+ binaryPath: join(packageDir, binaryFile),
101
+ version: packageJson.version,
102
+ };
103
+ } catch (error) {
104
+ fail(`Could not resolve optional platform package ${packageName}. Reinstall dependencies for ${process.platform}-${process.arch}.`);
105
+ }
106
+ }
107
+
108
+ function commandTargetPath(command) {
109
+ const suffix = process.platform === "win32" ? WINDOWS_EXECUTABLE_SUFFIX : "";
110
+ return join(commandTargetDir, `${command}${suffix}`);
111
+ }
112
+
113
+ function installedJsonPath() {
114
+ return join(commandTargetDir, INSTALLED_JSON);
115
+ }
116
+
117
+ function windowsTargetsAreCurrent(version) {
118
+ if (process.platform !== "win32") return false;
119
+ if (!COMMANDS.every((command) => existsSync(commandTargetPath(command)))) return false;
120
+
121
+ try {
122
+ const installed = JSON.parse(readFileSync(installedJsonPath(), "utf-8"));
123
+ return installed.version === version;
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ function linkOrCopyFirstExecutable(sourcePath, targetPath) {
130
+ mkdirSync(dirname(targetPath), { recursive: true });
131
+ const tempPath = `${targetPath}.${process.pid}.${Date.now()}.tmp${process.platform === "win32" ? WINDOWS_EXECUTABLE_SUFFIX : ""}`;
132
+ rmSync(tempPath, { force: true });
133
+
134
+ try {
135
+ try {
136
+ linkSync(sourcePath, tempPath);
137
+ } catch {
138
+ copyFileSync(sourcePath, tempPath);
139
+ }
140
+
141
+ chmodSync(tempPath, 0o755);
142
+ rmSync(targetPath, { force: true });
143
+ renameSync(tempPath, targetPath);
144
+ } finally {
145
+ rmSync(tempPath, { force: true });
146
+ }
147
+ }
148
+
149
+ function hardLinkCommandTarget(sourcePath, targetPath) {
150
+ if (sourcePath === targetPath) return;
151
+
152
+ const tempPath = `${targetPath}.${process.pid}.${Date.now()}.tmp${process.platform === "win32" ? WINDOWS_EXECUTABLE_SUFFIX : ""}`;
153
+ rmSync(tempPath, { force: true });
154
+
155
+ try {
156
+ linkSync(sourcePath, tempPath);
157
+ chmodSync(tempPath, 0o755);
158
+ rmSync(targetPath, { force: true });
159
+ renameSync(tempPath, targetPath);
160
+ } finally {
161
+ rmSync(tempPath, { force: true });
162
+ }
163
+ }
164
+
165
+ function repairCommandTargets() {
166
+ const platformPackage = currentPlatformPackage();
167
+
168
+ if (windowsTargetsAreCurrent(platformPackage.version)) {
169
+ return;
170
+ }
171
+
172
+ const firstTargetPath = commandTargetPath(COMMANDS[0]);
173
+ linkOrCopyFirstExecutable(platformPackage.binaryPath, firstTargetPath);
174
+
175
+ for (const command of COMMANDS.slice(1)) {
176
+ hardLinkCommandTarget(firstTargetPath, commandTargetPath(command));
177
+ }
178
+
179
+ if (process.platform === "win32") {
180
+ writeFileSync(installedJsonPath(), `${JSON.stringify({ version: platformPackage.version }, null, 2)}\n`);
181
+ }
182
+ }
183
+
184
+ function runExecutable(executablePath) {
185
+ const result = spawnSync(executablePath, process.argv.slice(2), { stdio: "inherit" });
186
+
187
+ if (result.error) {
188
+ fail(result.error.message);
189
+ }
190
+ if (result.status !== 0) {
191
+ process.exit(result.status ?? 1);
192
+ }
193
+ }
194
+
195
+ function removeInstallScript() {
196
+ try {
197
+ rmSync(rootInstallScriptPath, { force: true });
198
+ // TODO: should we also remove the postinstall script line from package.json?
199
+ } catch {
200
+ // Cleanup is best-effort; failing to remove install.js should not fail package installation.
201
+ }
202
+ }
203
+
204
+ repairCommandTargets();
205
+ removeInstallScript();
206
+ if (!IS_INSTALL_SCRIPT) {
207
+ const commandName = basename(launcherPath);
208
+ runExecutable(commandTargetPath(commandName));
209
+ }
package/package.json CHANGED
@@ -1,17 +1,8 @@
1
1
  {
2
2
  "name": "@ozyman42/ozy-cli",
3
- "version": "0.4.9-linux-x64.0",
4
- "type": "module",
5
- "os": [
6
- "linux"
7
- ],
8
- "cpu": [
9
- "x64"
10
- ],
11
- "files": [
12
- "multi-call-binary"
13
- ],
3
+ "version": "0.4.9",
14
4
  "description": "Tools for setting up new packages, configuring with git & npm",
5
+ "type": "module",
15
6
  "license": "MIT",
16
7
  "repository": {
17
8
  "type": "git",
@@ -20,5 +11,61 @@
20
11
  "publishConfig": {
21
12
  "access": "public",
22
13
  "provenance": true
14
+ },
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.js",
17
+ "types": "./src/index.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./src/index.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./entrypoints": null,
24
+ "./entrypoints/*": null,
25
+ "./agents": null,
26
+ "./agents/*": null,
27
+ "./internal": null,
28
+ "./internal/*": null,
29
+ "./*": {
30
+ "types": "./src/*.ts",
31
+ "import": "./dist/*.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "README.md",
36
+ "LICENSE",
37
+ "dist",
38
+ "src",
39
+ "bin",
40
+ "install.js"
41
+ ],
42
+ "scripts": {
43
+ "postinstall": "node ./install.js"
44
+ },
45
+ "bin": {
46
+ "ozy": "./bin/ozy",
47
+ "ozy-signing-agent": "./bin/ozy-signing-agent",
48
+ "ozy-ssh-keygen": "./bin/ozy-ssh-keygen",
49
+ "ozy-virtual-security-key": "./bin/ozy-virtual-security-key"
50
+ },
51
+ "dependencies": {
52
+ "@effect/platform-bun": "^4.0.0-beta.70",
53
+ "@ozyman42/interactive-cli-select": "^0.2.0",
54
+ "bip39": "^3.1.0",
55
+ "bs58": "^6.0.0",
56
+ "commander": "^13.1.0",
57
+ "effect": "4.0.0-beta.70",
58
+ "effective-modules": "0.3.1-effect4",
59
+ "expand-tilde": "^2.0.2",
60
+ "friendly-words": "^1.3.1",
61
+ "ssh-config": "^5.0.3",
62
+ "zod": "^3.24.2"
63
+ },
64
+ "optionalDependencies": {
65
+ "@ozyman42/ozy-cli-darwin-arm64": "npm:@ozyman42/ozy-cli@0.4.9-darwin-arm64.0",
66
+ "@ozyman42/ozy-cli-darwin-x64": "npm:@ozyman42/ozy-cli@0.4.9-darwin-x64.0",
67
+ "@ozyman42/ozy-cli-linux-arm64": "npm:@ozyman42/ozy-cli@0.4.9-linux-arm64.0",
68
+ "@ozyman42/ozy-cli-linux-x64": "npm:@ozyman42/ozy-cli@0.4.9-linux-x64.0",
69
+ "@ozyman42/ozy-cli-windows-x64": "npm:@ozyman42/ozy-cli@0.4.9-windows-x64.0"
23
70
  }
24
71
  }
@@ -0,0 +1,20 @@
1
+ import { Effect } from "effect";
2
+ import { makeCommand } from "@/common/command";
3
+ import { log } from "@/common/log";
4
+ import { commonModules } from "@/modules/common";
5
+ import { SSHConfigImpl } from "@/modules/common/ssh-config/impl";
6
+
7
+ export const hosts = makeCommand('hosts', 'show all configured github.com SSH hosts', () =>
8
+ Effect.gen(function* () {
9
+ const sshConfig = yield* commonModules.SSHConfig;
10
+ const config = yield* sshConfig.getSSHConfig();
11
+ const gitHosts: string[] = [];
12
+ for (const host in config) {
13
+ if (config[host]!.HostName === 'github.com') {
14
+ gitHosts.push(host);
15
+ }
16
+ }
17
+ log('Available hosts:');
18
+ gitHosts.forEach(host => { log(` - ${host}`); });
19
+ }).pipe(Effect.provide(SSHConfigImpl.Layer))
20
+ );
@@ -0,0 +1,9 @@
1
+ import { Command } from 'commander';
2
+ import { setup } from './setup';
3
+ import { hosts } from './hosts';
4
+
5
+ export const git = new Command('git')
6
+ .summary('setup git in repo for verified commits');
7
+
8
+ [setup, hosts]
9
+ .forEach(cmd => { git.addCommand(cmd) });
@@ -0,0 +1,182 @@
1
+ import { Command } from 'commander';
2
+ import { Effect, Layer, Option, pipe } from 'effect';
3
+ import { log } from '@/common/log';
4
+ import { cliModules, type CLIModules } from '@/modules/cli';
5
+ import { commonModules, CommonModules } from '@/modules/common';
6
+ import { effunct, type EffectGen } from 'effective-modules';
7
+ import { GitImpl } from '@/modules/cli/git/impl';
8
+ import { GitHubImpl } from '@/modules/cli/github/impl';
9
+ import { AgentClientImpl } from '@/modules/cli/agent-client/impl';
10
+ import { OSPlatformImpl } from '@/modules/common/os-platform/impl';
11
+ import { SSHConfigImpl } from '@/modules/common/ssh-config/impl';
12
+ import { AGENT_SOCK_FILE_PATH, FUTURE_TOOL_NAME, STANDARD_REMOTE_PREFIX } from '../../common/constants';
13
+ import { CredentialId } from '@/modules/common/crypto/impl';
14
+ import { BunFileSystem } from "@effect/platform-bun";
15
+ import type { GitUser } from '@/modules/cli/git/interface';
16
+
17
+ type AllModules = CLIModules.Git | CLIModules.GitHub | CLIModules.AgentClient | CommonModules.SSHConfig;
18
+
19
+ const GITHUB_CLIENT_ID = 'Ov23liKYxk1Ag7SsNhbP';
20
+ const GITHUB_CLIENT_SECRET = 'e2901fbe93c591e7a53a903e70490ff87e998159';
21
+ const OAUTH_SCOPES = 'read:user user:email write:ssh_signing_key write:public_key';
22
+ const KEY_PREFIX = `${FUTURE_TOOL_NAME}-`;
23
+
24
+ function parseCredentialIdFromTitle(title: string): Option.Option<string> {
25
+ const match = title.match(/\(([^)]+)\)$/);
26
+ if (!match) return Option.none();
27
+ return Option.some(match[1]!);
28
+ }
29
+
30
+ function baseKey(pubkey: string): string {
31
+ return pubkey.split(' ').slice(0, 2).join(' ');
32
+ }
33
+
34
+ export enum GitSetupError {
35
+ NotInGitRepositoryError = 'NotInGitRepositoryError',
36
+ NoRemoteOriginError = 'NoRemoteOriginError',
37
+ OAuthFailedError = 'OAuthFailedError',
38
+ GitHubUserFetchFailedError = 'GitHubUserFetchFailedError',
39
+ RepoAccessDeniedError = 'RepoAccessDeniedError',
40
+ MultipleJiveSigningKeysError = 'MultipleJiveSigningKeysError',
41
+ MultipleJiveAuthnKeysError = 'MultipleJiveAuthnKeysError',
42
+ MissingCredentialIdError = 'MissingCredentialIdError',
43
+ SigningAuthnKeyMismatchError = 'SigningAuthnKeyMismatchError',
44
+ AgentStartFailedError = 'AgentStartFailedError',
45
+ AgentProcedureFailedError = 'AgentProcedureFailedError',
46
+ GitCloneFailedError = 'GitCloneFailedError',
47
+ GitConfigFailedError = 'GitConfigFailedError',
48
+ }
49
+
50
+ function* gitSetup(ownerRepoArg?: string): Effect.fn.Return<void, string, AllModules> {
51
+ const git = yield* cliModules.Git;
52
+ const github = yield* cliModules.GitHub;
53
+ const agentClient = yield* cliModules.AgentClient;
54
+ const sshConfig = yield* commonModules.SSHConfig;
55
+ const isCloneMode = ownerRepoArg !== undefined;
56
+
57
+ // 1. Parse owner/repo
58
+ const { owner, repo } = yield* git.resolveOwnerAndRepo(Option.fromNullishOr(ownerRepoArg));
59
+ log(`Setting up ${owner}/${repo}`);
60
+
61
+ // 2. GitHub OAuth
62
+ const token = yield* github.authorize(GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, OAUTH_SCOPES);
63
+ log('✓ Authorized with GitHub');
64
+
65
+ // 3. Fetch user info
66
+ const { login, email } = yield* github.getUser(token);
67
+ const gitUser: GitUser = {name: login, email: email};
68
+ log(` Logged in as: ${gitUser.name} <${gitUser.email}>`);
69
+
70
+ // 4. Check GitHub signing keys for <tool name>: prefix
71
+ const signingKeys = yield* github.getSigningKeys(token);
72
+ const toolKeys = signingKeys.filter(k => k.title.startsWith(KEY_PREFIX));
73
+ if (toolKeys.length > 1)
74
+ return yield* Effect.fail(`${GitSetupError.MultipleJiveSigningKeysError}: Found ${toolKeys.length} signing keys with "${KEY_PREFIX}" prefix — expected at most 1`);
75
+ let maybeKey: Option.Option<{ credentialId: string; pubkey: string }> = Option.none();
76
+ if (toolKeys[0]) {
77
+ const credentialId = parseCredentialIdFromTitle(toolKeys[0].title);
78
+ if (Option.isNone(credentialId))
79
+ return yield* Effect.fail(`${GitSetupError.MissingCredentialIdError}: signing key title "${toolKeys[0].title}" has no credentialId`);
80
+ maybeKey = Option.some({ credentialId: credentialId.value, pubkey: baseKey(toolKeys[0].key) });
81
+ }
82
+
83
+ // 5. Check authn keys too — must match signing key if both present.
84
+ const authnKeys = yield* github.getAuthnKeys(token);
85
+ const toolAuthnKeys = authnKeys.filter(k => k.title.startsWith(KEY_PREFIX));
86
+ if (toolAuthnKeys.length > 1)
87
+ return yield* Effect.fail(`${GitSetupError.MultipleJiveAuthnKeysError}: Found ${toolAuthnKeys.length} authn keys with "${KEY_PREFIX}" prefix — expected at most 1`);
88
+ let maybeAuthnKey: Option.Option<{ credentialId: string; pubkey: string }> = Option.none();
89
+ if (toolAuthnKeys[0]) {
90
+ const credentialId = parseCredentialIdFromTitle(toolAuthnKeys[0].title);
91
+ if (Option.isNone(credentialId))
92
+ return yield* Effect.fail(`${GitSetupError.MissingCredentialIdError}: authn key title "${toolAuthnKeys[0].title}" has no credentialId`);
93
+ maybeAuthnKey = Option.some({ credentialId: credentialId.value, pubkey: baseKey(toolAuthnKeys[0].key) });
94
+ }
95
+ if (Option.isSome(maybeKey) && Option.isSome(maybeAuthnKey) && maybeKey.value.pubkey !== maybeAuthnKey.value.pubkey)
96
+ return yield* Effect.fail(`${GitSetupError.SigningAuthnKeyMismatchError}: signing key and authn key pubkeys don't match — GitHub account is in an inconsistent state`);
97
+
98
+ // 6. Call agent to get/create/verify key (starts agent if not running)
99
+ const { credentialId, pubkey } = yield* pipe(
100
+ Effect.gen(() => agentClient.usingClient(function* (client): EffectGen<{ pubkey: string; credentialId: string }, string> {
101
+ return yield* pipe(
102
+ client.Setup({
103
+ pubkey: Option.map(maybeKey, k => k.pubkey),
104
+ credentialId: Option.map(maybeKey, k => k.credentialId),
105
+ username: login,
106
+ }),
107
+ Effect.mapError(e => `${GitSetupError.AgentProcedureFailedError}: ${String(e)}`)
108
+ );
109
+ })),
110
+ Effect.catchTag("AgentClientError", err => Effect.fail(err.reason)),
111
+ Effect.catchTag("AgentClientUsageError", err => Effect.fail(err.cause))
112
+ );
113
+ const credentialFriendlyName = new CredentialId(credentialId).humanReadableName;
114
+ const keyTitle = `${credentialFriendlyName} (${credentialId})`;
115
+
116
+ // 8. Register signing key on GitHub if not already present
117
+ if (Option.isNone(maybeKey)) {
118
+ yield* github.addSigningKey(token, keyTitle, pubkey);
119
+ log(`✓ Registered GitHub signing key: ${keyTitle}`);
120
+ }
121
+
122
+ // 9. Register key as authn key on GitHub account if not already present
123
+ if (Option.isNone(maybeAuthnKey)) {
124
+ yield* github.addAuthnKey(token, keyTitle, pubkey);
125
+ log(`✓ Registered authn key on GitHub account`);
126
+ }
127
+
128
+ // 10. Write ssh config (pubkey file written by agent via KeyMapStore.addKey)
129
+ const pubkeyPath = sshConfig.getPubkeyPath(credentialFriendlyName);
130
+ yield* sshConfig.writeHost({
131
+ section: {
132
+ HostName: "github.com",
133
+ User: "git",
134
+ IdentityFile: pubkeyPath,
135
+ IdentitiesOnly: "yes",
136
+ IdentityAgent: AGENT_SOCK_FILE_PATH,
137
+ },
138
+ });
139
+
140
+ // 11. Clone if in clone mode
141
+ if (isCloneMode) {
142
+ yield* git.clone(owner, repo);
143
+ log(`✓ Cloned into ${repo}/`);
144
+ }
145
+
146
+ // 12. Set repo config plus any submodules' config
147
+ log('\nSetting git config:');
148
+ yield* git.setupRepo({
149
+ dir: process.cwd(),
150
+ remoteURL: `${STANDARD_REMOTE_PREFIX}${owner}/${repo}.git`,
151
+ user: gitUser,
152
+ pubkeyPath,
153
+ });
154
+
155
+ log(`\n✓ Setup complete for ${owner}/${repo}`);
156
+ }
157
+
158
+ const layerLive = pipe(
159
+ GitImpl.Layer,
160
+ Layer.provideMerge(GitHubImpl.Layer),
161
+ Layer.provideMerge(AgentClientImpl.Layer),
162
+ Layer.provideMerge(OSPlatformImpl.Layer),
163
+ Layer.provideMerge(SSHConfigImpl.Layer),
164
+ Layer.provideMerge(BunFileSystem.layer)
165
+ )
166
+
167
+ export const setup = new Command('setup')
168
+ .description('setup verified git commits for current repo')
169
+ .argument('[owner/repo]', 'GitHub repo to clone and set up (omit if already in a repo with a remote)')
170
+ .action(async (ownerRepo?: string) => {
171
+ function* program() {
172
+ yield* pipe(
173
+ effunct(gitSetup)(ownerRepo),
174
+ Effect.provide(layerLive),
175
+ Effect.catch(err => Effect.gen(function* () {
176
+ log(`✗ ${err}`);
177
+ }))
178
+ );
179
+ }
180
+ await Effect.runPromise(Effect.gen(program));
181
+ process.exit();
182
+ });
@@ -0,0 +1,15 @@
1
+ import { program } from 'commander';
2
+ import { git } from './git';
3
+ import { npm } from './npm';
4
+ import { upgrade } from './upgrade';
5
+ import { ssh } from "./ssh";
6
+ import { CLI_CMD_NAME, CURRENT_VERSION } from '@/common/constants';
7
+
8
+ program.name(CLI_CMD_NAME).version(CURRENT_VERSION);
9
+
10
+ [git, npm, ssh, upgrade]
11
+ .forEach(cmd => { program.addCommand(cmd) });
12
+
13
+ program.parse();
14
+
15
+ if (process.argv.length <= 2) program.help();
@@ -0,0 +1,8 @@
1
+ import { Command } from 'commander';
2
+ import { setup } from './setup';
3
+
4
+ export const npm = new Command('npm')
5
+ .summary('npm package management utilities');
6
+
7
+ [setup]
8
+ .forEach(cmd => { npm.addCommand(cmd) });
@@ -0,0 +1,78 @@
1
+ import { $ } from 'bun';
2
+ import { Effect, Option } from 'effect';
3
+
4
+ export interface RepoAccess {
5
+ owner: string;
6
+ repo: string;
7
+ canPush: boolean;
8
+ }
9
+
10
+ export interface CreatedRepo {
11
+ owner: string;
12
+ repo: string;
13
+ url: string;
14
+ sshUrl: string;
15
+ }
16
+
17
+ // Handles https://github.com/owner/repo.git, git@github.com:owner/repo.git,
18
+ // and SSH aliases like github-ozyman42:owner/repo.git
19
+ export function parseGithubOwnerRepo(url: string): Option.Option<{ owner: string; repo: string }> {
20
+ const https = url.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?$/);
21
+ if (https) return Option.some({ owner: https[1]!, repo: https[2]! });
22
+ const alias = url.match(/^[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/);
23
+ if (alias) return Option.some({ owner: alias[1]!, repo: alias[2]! });
24
+ return Option.none();
25
+ }
26
+
27
+ async function viewRepo(nameWithOwner: string): Promise<Option.Option<CreatedRepo>> {
28
+ const view = await $`gh repo view ${nameWithOwner} --json name,owner,url,sshUrl`.quiet().nothrow();
29
+ if (view.exitCode !== 0) return Option.none();
30
+ try {
31
+ const data = JSON.parse(view.stdout.toString());
32
+ return Option.some({ owner: data.owner.login, repo: data.name, url: data.url, sshUrl: data.sshUrl });
33
+ } catch {
34
+ return Option.none();
35
+ }
36
+ }
37
+
38
+ export function ensureGitHubRepo(name: string, isPrivate = false): Effect.Effect<{ repo: CreatedRepo; created: boolean }, string> {
39
+ return Effect.tryPromise({
40
+ try: async () => {
41
+ const whoami = await $`gh api user --jq .login`.quiet().nothrow();
42
+ const owner = whoami.exitCode === 0 ? whoami.stdout.toString().trim() : null;
43
+
44
+ if (owner) {
45
+ const existingOption = await viewRepo(`${owner}/${name}`);
46
+ if (Option.isSome(existingOption)) return { repo: existingOption.value, created: false };
47
+ }
48
+
49
+ const visibility = isPrivate ? '--private' : '--public';
50
+ const create = await $`gh repo create ${name} ${visibility}`.quiet().nothrow();
51
+ if (create.exitCode !== 0) {
52
+ throw new Error(`gh repo create failed: ${create.stderr.toString().trim()}`);
53
+ }
54
+ const createdOption = await viewRepo(name);
55
+ if (Option.isNone(createdOption)) throw new Error(`repo created but could not retrieve details for "${name}"`);
56
+ return { repo: createdOption.value, created: true };
57
+ },
58
+ catch: (e) => e instanceof Error ? e.message : String(e),
59
+ });
60
+ }
61
+
62
+ export async function checkGitHubAccess(remoteUrl: string): Promise<Option.Option<RepoAccess>> {
63
+ const parsedOption = parseGithubOwnerRepo(remoteUrl);
64
+ if (Option.isNone(parsedOption)) return Option.none();
65
+ const { owner, repo } = parsedOption.value;
66
+
67
+ const result = await $`gh api repos/${owner}/${repo}`.quiet().nothrow();
68
+ if (result.exitCode !== 0) return Option.none();
69
+
70
+ let data: any;
71
+ try {
72
+ data = JSON.parse(result.stdout.toString());
73
+ } catch {
74
+ return Option.none();
75
+ }
76
+
77
+ return Option.some({ owner, repo, canPush: data?.permissions?.push === true });
78
+ }