@ozyman42/ozy-cli 0.4.8-windows-x64.0 → 0.4.8

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 (207) hide show
  1. package/bin/ozy +191 -0
  2. package/bin/ozy-signing-agent +191 -0
  3. package/bin/ozy-ssh-keygen +191 -0
  4. package/bin/ozy-virtual-security-key +191 -0
  5. package/dist/commands/git/hosts.js +18 -0
  6. package/dist/commands/git/hosts.js.map +1 -0
  7. package/dist/commands/git/index.js +8 -0
  8. package/dist/commands/git/index.js.map +1 -0
  9. package/dist/commands/git/setup.js +145 -0
  10. package/dist/commands/git/setup.js.map +1 -0
  11. package/dist/commands/index.js +13 -0
  12. package/dist/commands/index.js.map +1 -0
  13. package/dist/commands/npm/index.js +7 -0
  14. package/dist/commands/npm/index.js.map +1 -0
  15. package/dist/commands/npm/setup/git-remote.js +66 -0
  16. package/dist/commands/npm/setup/git-remote.js.map +1 -0
  17. package/dist/commands/npm/setup/index.js +168 -0
  18. package/dist/commands/npm/setup/index.js.map +1 -0
  19. package/dist/commands/npm/setup/npm-auth.js +62 -0
  20. package/dist/commands/npm/setup/npm-auth.js.map +1 -0
  21. package/dist/commands/npm/setup/package-json-validate.js +47 -0
  22. package/dist/commands/npm/setup/package-json-validate.js.map +1 -0
  23. package/dist/commands/npm/setup/package-registry.js +20 -0
  24. package/dist/commands/npm/setup/package-registry.js.map +1 -0
  25. package/dist/commands/npm/setup/setup.test.js +95 -0
  26. package/dist/commands/npm/setup/setup.test.js.map +1 -0
  27. package/dist/commands/npm/setup/trusted-publishing.js +84 -0
  28. package/dist/commands/npm/setup/trusted-publishing.js.map +1 -0
  29. package/dist/commands/npm/setup/workflow.js +19 -0
  30. package/dist/commands/npm/setup/workflow.js.map +1 -0
  31. package/dist/commands/ssh/get-sk-credential.js +104 -0
  32. package/dist/commands/ssh/get-sk-credential.js.map +1 -0
  33. package/dist/commands/ssh/index.js +7 -0
  34. package/dist/commands/ssh/index.js.map +1 -0
  35. package/dist/commands/upgrade.js +199 -0
  36. package/dist/commands/upgrade.js.map +1 -0
  37. package/dist/common/command.js +15 -0
  38. package/dist/common/command.js.map +1 -0
  39. package/dist/common/constants.js +25 -0
  40. package/dist/common/constants.js.map +1 -0
  41. package/dist/common/effective-modules-extensions.js +13 -0
  42. package/dist/common/effective-modules-extensions.js.map +1 -0
  43. package/dist/common/log.js +4 -0
  44. package/dist/common/log.js.map +1 -0
  45. package/dist/common/render-caller-tree.js +20 -0
  46. package/dist/common/render-caller-tree.js.map +1 -0
  47. package/dist/entrypoints/ozy-signing-agent.js +48 -0
  48. package/dist/entrypoints/ozy-signing-agent.js.map +1 -0
  49. package/dist/entrypoints/ozy-ssh-keygen.js +9 -0
  50. package/dist/entrypoints/ozy-ssh-keygen.js.map +1 -0
  51. package/dist/entrypoints/ozy-virtual-security-key.js +18 -0
  52. package/dist/entrypoints/ozy-virtual-security-key.js.map +1 -0
  53. package/dist/entrypoints/ozy.js +3 -0
  54. package/dist/entrypoints/ozy.js.map +1 -0
  55. package/dist/index.js +2 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/internal/thing.js +4 -0
  58. package/dist/internal/thing.js.map +1 -0
  59. package/dist/modules/cli/agent-client/impl.js +109 -0
  60. package/dist/modules/cli/agent-client/impl.js.map +1 -0
  61. package/dist/modules/cli/agent-client/interface.js +11 -0
  62. package/dist/modules/cli/agent-client/interface.js.map +1 -0
  63. package/dist/modules/cli/git/impl.js +124 -0
  64. package/dist/modules/cli/git/impl.js.map +1 -0
  65. package/dist/modules/cli/git/interface.js +3 -0
  66. package/dist/modules/cli/git/interface.js.map +1 -0
  67. package/dist/modules/cli/git/submodule/fix.js +257 -0
  68. package/dist/modules/cli/git/submodule/fix.js.map +1 -0
  69. package/dist/modules/cli/git/submodule/git-state.js +154 -0
  70. package/dist/modules/cli/git/submodule/git-state.js.map +1 -0
  71. package/dist/modules/cli/git/submodule/gitmodules.js +113 -0
  72. package/dist/modules/cli/git/submodule/gitmodules.js.map +1 -0
  73. package/dist/modules/cli/git/submodule/printer.js +232 -0
  74. package/dist/modules/cli/git/submodule/printer.js.map +1 -0
  75. package/dist/modules/cli/git/submodule/resolve-git-dir.js +16 -0
  76. package/dist/modules/cli/git/submodule/resolve-git-dir.js.map +1 -0
  77. package/dist/modules/cli/github/impl.js +137 -0
  78. package/dist/modules/cli/github/impl.js.map +1 -0
  79. package/dist/modules/cli/github/interface.js +2 -0
  80. package/dist/modules/cli/github/interface.js.map +1 -0
  81. package/dist/modules/cli/index.js +9 -0
  82. package/dist/modules/cli/index.js.map +1 -0
  83. package/dist/modules/common/crypto/impl.js +133 -0
  84. package/dist/modules/common/crypto/impl.js.map +1 -0
  85. package/dist/modules/common/crypto/interface.js +2 -0
  86. package/dist/modules/common/crypto/interface.js.map +1 -0
  87. package/dist/modules/common/index.js +10 -0
  88. package/dist/modules/common/index.js.map +1 -0
  89. package/dist/modules/common/kep-map-store/impl.js +42 -0
  90. package/dist/modules/common/kep-map-store/impl.js.map +1 -0
  91. package/dist/modules/common/kep-map-store/interface.js +2 -0
  92. package/dist/modules/common/kep-map-store/interface.js.map +1 -0
  93. package/dist/modules/common/os-platform/caller-info.js +132 -0
  94. package/dist/modules/common/os-platform/caller-info.js.map +1 -0
  95. package/dist/modules/common/os-platform/impl.js +95 -0
  96. package/dist/modules/common/os-platform/impl.js.map +1 -0
  97. package/dist/modules/common/os-platform/interface.js +2 -0
  98. package/dist/modules/common/os-platform/interface.js.map +1 -0
  99. package/dist/modules/common/os-platform/virtual-hid/index.js +12 -0
  100. package/dist/modules/common/os-platform/virtual-hid/index.js.map +1 -0
  101. package/dist/modules/common/os-platform/virtual-hid/linux.js +5 -0
  102. package/dist/modules/common/os-platform/virtual-hid/linux.js.map +1 -0
  103. package/dist/modules/common/os-platform/virtual-hid/mac.js +119 -0
  104. package/dist/modules/common/os-platform/virtual-hid/mac.js.map +1 -0
  105. package/dist/modules/common/os-platform/virtual-hid/windows.js +5 -0
  106. package/dist/modules/common/os-platform/virtual-hid/windows.js.map +1 -0
  107. package/dist/modules/common/ssh-config/impl.js +116 -0
  108. package/dist/modules/common/ssh-config/impl.js.map +1 -0
  109. package/dist/modules/common/ssh-config/interface.js +10 -0
  110. package/dist/modules/common/ssh-config/interface.js.map +1 -0
  111. package/dist/modules/ssh-agent/index.js +8 -0
  112. package/dist/modules/ssh-agent/index.js.map +1 -0
  113. package/dist/modules/ssh-agent/session/impl.js +265 -0
  114. package/dist/modules/ssh-agent/session/impl.js.map +1 -0
  115. package/dist/modules/ssh-agent/session/interface-rpc.js +20 -0
  116. package/dist/modules/ssh-agent/session/interface-rpc.js.map +1 -0
  117. package/dist/modules/ssh-agent/session/interface.js +32 -0
  118. package/dist/modules/ssh-agent/session/interface.js.map +1 -0
  119. package/dist/modules/ssh-agent/session/passkey-prf-page.js +204 -0
  120. package/dist/modules/ssh-agent/session/passkey-prf-page.js.map +1 -0
  121. package/dist/modules/ssh-agent/session/prf-flow.js +103 -0
  122. package/dist/modules/ssh-agent/session/prf-flow.js.map +1 -0
  123. package/dist/modules/ssh-agent/ssh-agent/impl.js +117 -0
  124. package/dist/modules/ssh-agent/ssh-agent/impl.js.map +1 -0
  125. package/dist/modules/ssh-agent/ssh-agent/interface.js +2 -0
  126. package/dist/modules/ssh-agent/ssh-agent/interface.js.map +1 -0
  127. package/dist/scripts/build.js +410 -0
  128. package/dist/scripts/build.js.map +1 -0
  129. package/dist/scripts/check-npm-version.js +8 -0
  130. package/dist/scripts/check-npm-version.js.map +1 -0
  131. package/dist/scripts/kill-existing-agent.js +21 -0
  132. package/dist/scripts/kill-existing-agent.js.map +1 -0
  133. package/dist/scripts/publish.js +46 -0
  134. package/dist/scripts/publish.js.map +1 -0
  135. package/package.json +52 -11
  136. package/src/commands/git/hosts.ts +20 -0
  137. package/src/commands/git/index.ts +9 -0
  138. package/src/commands/git/setup.ts +182 -0
  139. package/src/commands/index.ts +15 -0
  140. package/src/commands/npm/index.ts +8 -0
  141. package/src/commands/npm/setup/git-remote.ts +78 -0
  142. package/src/commands/npm/setup/index.ts +189 -0
  143. package/src/commands/npm/setup/npm-auth.ts +58 -0
  144. package/src/commands/npm/setup/package-json-validate.ts +68 -0
  145. package/src/commands/npm/setup/package-registry.ts +24 -0
  146. package/src/commands/npm/setup/publish-workflow.yml +35 -0
  147. package/src/commands/npm/setup/setup.test.ts +117 -0
  148. package/src/commands/npm/setup/trusted-publishing.ts +106 -0
  149. package/src/commands/npm/setup/workflow.ts +20 -0
  150. package/src/commands/ssh/get-sk-credential.ts +110 -0
  151. package/src/commands/ssh/index.ts +8 -0
  152. package/src/commands/upgrade.ts +220 -0
  153. package/src/common/command.ts +16 -0
  154. package/src/common/constants.ts +26 -0
  155. package/src/common/effective-modules-extensions.ts +24 -0
  156. package/src/common/log.ts +3 -0
  157. package/src/common/render-caller-tree.ts +22 -0
  158. package/src/entrypoints/ozy-signing-agent.ts +48 -0
  159. package/src/entrypoints/ozy-ssh-keygen.ts +10 -0
  160. package/src/entrypoints/ozy-virtual-security-key.ts +21 -0
  161. package/src/entrypoints/ozy.ts +2 -0
  162. package/src/index.ts +1 -0
  163. package/src/internal/thing.ts +3 -0
  164. package/src/modules/cli/agent-client/impl.ts +152 -0
  165. package/src/modules/cli/agent-client/interface.ts +17 -0
  166. package/src/modules/cli/git/impl.ts +133 -0
  167. package/src/modules/cli/git/interface.ts +23 -0
  168. package/src/modules/cli/git/submodule/fix.ts +298 -0
  169. package/src/modules/cli/git/submodule/git-state.ts +217 -0
  170. package/src/modules/cli/git/submodule/gitmodules.ts +126 -0
  171. package/src/modules/cli/git/submodule/printer.ts +256 -0
  172. package/src/modules/cli/git/submodule/resolve-git-dir.ts +14 -0
  173. package/src/modules/cli/github/impl.ts +169 -0
  174. package/src/modules/cli/github/interface.ts +29 -0
  175. package/src/modules/cli/index.ts +16 -0
  176. package/src/modules/common/crypto/impl.ts +173 -0
  177. package/src/modules/common/crypto/interface.ts +20 -0
  178. package/src/modules/common/index.ts +19 -0
  179. package/src/modules/common/kep-map-store/impl.ts +47 -0
  180. package/src/modules/common/kep-map-store/interface.ts +9 -0
  181. package/src/modules/common/os-platform/caller-info.ts +152 -0
  182. package/src/modules/common/os-platform/impl.ts +91 -0
  183. package/src/modules/common/os-platform/interface.ts +16 -0
  184. package/src/modules/common/os-platform/virtual-hid/index.ts +12 -0
  185. package/src/modules/common/os-platform/virtual-hid/linux.ts +7 -0
  186. package/src/modules/common/os-platform/virtual-hid/mac.ts +150 -0
  187. package/src/modules/common/os-platform/virtual-hid/windows.ts +7 -0
  188. package/src/modules/common/ssh-config/impl.ts +130 -0
  189. package/src/modules/common/ssh-config/interface.ts +33 -0
  190. package/src/modules/ssh-agent/index.ts +13 -0
  191. package/src/modules/ssh-agent/session/agent-notes.md +210 -0
  192. package/src/modules/ssh-agent/session/design.md +2 -0
  193. package/src/modules/ssh-agent/session/html-refactor.md +23 -0
  194. package/src/modules/ssh-agent/session/impl.ts +328 -0
  195. package/src/modules/ssh-agent/session/interface-rpc.ts +24 -0
  196. package/src/modules/ssh-agent/session/interface.ts +62 -0
  197. package/src/modules/ssh-agent/session/passkey-prf-page.ts +224 -0
  198. package/src/modules/ssh-agent/session/prf-flow.ts +152 -0
  199. package/src/modules/ssh-agent/ssh-agent/impl.ts +136 -0
  200. package/src/modules/ssh-agent/ssh-agent/interface.ts +9 -0
  201. package/src/scripts/build.ts +504 -0
  202. package/src/scripts/check-npm-version.ts +7 -0
  203. package/src/scripts/command-launcher.js.ejs +168 -0
  204. package/src/scripts/kill-existing-agent.ts +22 -0
  205. package/src/scripts/publish.ts +47 -0
  206. package/src/scripts/windows-bin-shims.md +1104 -0
  207. package/multi-call-binary.exe +0 -0
@@ -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
+ });
@@ -0,0 +1,2 @@
1
+ // TODO: we should move commands to its own layer
2
+ import '@/commands';
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export function time() {
2
+ console.log("is with us all");
3
+ }
@@ -0,0 +1,152 @@
1
+ import { Effect, Layer, Match, Option, pipe, Data, FileSystem, Schedule, Result } from "effect";
2
+ import { effunct, implementing, type EffectGen } from "effective-modules";
3
+ import { FetchHttpClient } from "effect/unstable/http";
4
+ import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
5
+ import { AgentRpcGroup } from "@/modules/ssh-agent/session/interface-rpc";
6
+ import { cliModules } from "..";
7
+ import { type AgentRpcClient, type IAgentClient, AgentClientError } from "./interface";
8
+ import { AGENT_CMD_PATH, AGENT_PID_FILE_PATH, AGENT_PORT_FILE_PATH, AGENT_SOCK_FILE_PATH, CURRENT_VERSION } from "@/common/constants";
9
+ import { readFileSync, rmSync } from "node:fs";
10
+ import { log } from "@/common/log";
11
+
12
+ function makeAgentClientLayer(port: number) {
13
+ return pipe(
14
+ RpcClient.layerProtocolHttp({ url: `http://localhost:${port}/rpc` }),
15
+ Layer.provideMerge(RpcSerialization.layerJson),
16
+ Layer.provideMerge(FetchHttpClient.layer)
17
+ );
18
+ }
19
+
20
+ class NoResponseError extends Data.TaggedError("NoResponseError")<{}> {
21
+ public toString() { return "No response from agent."; }
22
+ }
23
+
24
+ class NonMatchingVersionError extends Data.TaggedError("NonMatchingVersionError")<{
25
+ cliVersion: string;
26
+ agentVersion: string;
27
+ }> {
28
+ public toString() {
29
+ return `Agent running but with mismatched version ${this.agentVersion} compared to CLI version ${this.cliVersion}`;
30
+ }
31
+ }
32
+
33
+ type PingError = NoResponseError | NonMatchingVersionError;
34
+
35
+ export class AgentClientImpl extends implementing(cliModules.AgentClient).uses(FileSystem.FileSystem) implements IAgentClient {
36
+ private *readPortFromFile(): EffectGen<Option.Option<number>, never> {
37
+ const result = yield* pipe(
38
+ this.getDependency(FileSystem.FileSystem).readFileString(AGENT_PORT_FILE_PATH),
39
+ Effect.result
40
+ );
41
+ if (Result.isFailure(result)) return Option.none();
42
+ const port = parseInt(result.success.trim());
43
+ if (isNaN(port) || port <= 0 || port > 65535) {
44
+ log(`Read out of bounds port ${port}`);
45
+ return Option.none();
46
+ }
47
+ return Option.some(port);
48
+ }
49
+
50
+ private *pingAtPort(port: number): EffectGen<void, PingError> {
51
+ yield* Effect.scoped(
52
+ Effect.gen(function* () {
53
+ const client = yield* RpcClient.make(AgentRpcGroup);
54
+ const { version } = yield* pipe(
55
+ client.GetVersion({}),
56
+ Effect.catchTag("RpcClientError", () => new NoResponseError())
57
+ );
58
+ if (version !== CURRENT_VERSION)
59
+ yield* new NonMatchingVersionError({ cliVersion: CURRENT_VERSION, agentVersion: version });
60
+ })
61
+ ).pipe(Effect.provide(makeAgentClientLayer(port)));
62
+ }
63
+
64
+ private *ping(): EffectGen<{ port: number }, PingError> {
65
+ const portOption = yield* effunct(this.readPortFromFile)();
66
+ if (Option.isNone(portOption)) return yield* new NoResponseError();
67
+ const port = portOption.value;
68
+ yield* effunct(this.pingAtPort)(port);
69
+ return { port };
70
+ }
71
+
72
+ private *waitForPidFile(expectedPid: number): EffectGen<void, string> {
73
+ const fs = this.getDependency(FileSystem.FileSystem);
74
+ yield* pipe(
75
+ Effect.retry(
76
+ pipe(
77
+ fs.readFileString(AGENT_PID_FILE_PATH),
78
+ Effect.catchTag("PlatformError", () => Effect.fail("pid file not yet written")),
79
+ Effect.flatMap(content => {
80
+ const pid = parseInt(content.trim());
81
+ return pid === expectedPid
82
+ ? Effect.void
83
+ : Effect.fail(`PID mismatch: expected ${expectedPid}, got ${pid}`);
84
+ })
85
+ ),
86
+ Schedule.spaced("50 millis")
87
+ ),
88
+ Effect.timeout("5 seconds"),
89
+ Effect.mapError(() => "Agent did not start within 5 seconds")
90
+ );
91
+ }
92
+
93
+ private *startAgent(): EffectGen<{ pid: number; port: number }, string | PingError> {
94
+ const proc = Bun.spawn([AGENT_CMD_PATH], { stdout: "ignore", stderr: "ignore" });
95
+ yield* effunct(this.waitForPidFile)(proc.pid);
96
+ const { port } = yield* effunct(this.ping)();
97
+ log(`Started agent at PID ${proc.pid}`);
98
+ return { pid: proc.pid, port };
99
+ }
100
+
101
+ *killIfRunning(): EffectGen<void, string> {
102
+ try {
103
+ // Throws if file missing
104
+ const pidFileContent = readFileSync(AGENT_PID_FILE_PATH, "utf-8");
105
+ const PID = parseInt(pidFileContent.trim());
106
+ // If doesn't exist, below line throws
107
+ process.kill(PID, 0);
108
+ log(`Killing agent on PID ${PID}`);
109
+ process.kill(PID, "SIGTERM");
110
+ yield* Effect.sleep(500);
111
+ } catch { }
112
+ try { rmSync(AGENT_PID_FILE_PATH); } catch {}
113
+ try { rmSync(AGENT_PORT_FILE_PATH); } catch {}
114
+ try { rmSync(AGENT_SOCK_FILE_PATH); } catch {}
115
+ }
116
+
117
+ private *ensureRunning(): EffectGen<{ port: number }, string> {
118
+ const pingResult = yield* pipe(effunct(this.ping)(), Effect.result);
119
+ if (Result.isSuccess(pingResult)) return pingResult.success;
120
+ log(pingResult.failure.toString());
121
+ yield* Match.value(pingResult.failure).pipe(
122
+ Match.tag("NonMatchingVersionError", () => effunct(this.killIfRunning)()),
123
+ Match.tag("NoResponseError", () => effunct(this.killIfRunning)()),
124
+ Match.exhaustive
125
+ );
126
+ return yield* pipe(
127
+ effunct(this.startAgent)(),
128
+ Effect.mapError(err =>
129
+ typeof err === "string" ?
130
+ err :
131
+ `Failed to start agent due to ${err.name}: ${err.toString()}`
132
+ )
133
+ );
134
+ }
135
+
136
+ *usingClient<A, E>(use: (client: AgentRpcClient) => EffectGen<A, E>): EffectGen<A, AgentClientError<E>> {
137
+ const { port } = yield* pipe(
138
+ effunct(this.ensureRunning)(),
139
+ Effect.mapError(reason => new AgentClientError.ClientError({ reason }))
140
+ );
141
+ return yield* pipe(
142
+ Effect.scoped(
143
+ Effect.gen(function* () {
144
+ const client = yield* RpcClient.make(AgentRpcGroup);
145
+ return yield* use(client);
146
+ })
147
+ ),
148
+ Effect.provide(makeAgentClientLayer(port)),
149
+ Effect.mapError(e => new AgentClientError.UsageError({ cause: e }))
150
+ );
151
+ }
152
+ }
@@ -0,0 +1,17 @@
1
+ import type { EffectGen } from "effective-modules";
2
+ import type { AgentRpcGroup } from "@/modules/ssh-agent/session/interface-rpc";
3
+ import type { RpcClient, RpcClientError as RpcClientErrorModule, RpcGroup } from "effect/unstable/rpc";
4
+ import { Data } from "effect";
5
+
6
+ export type AgentRpcClient = RpcClient.RpcClient<RpcGroup.Rpcs<AgentRpcGroup>, RpcClientErrorModule.RpcClientError>;
7
+
8
+ export namespace AgentClientError {
9
+ export class UsageError<E> extends Data.TaggedError("AgentClientUsageError")<{ readonly cause: E }> {}
10
+ export class ClientError extends Data.TaggedError("AgentClientError")<{ readonly reason: string }> {}
11
+ }
12
+ export type AgentClientError<E> = AgentClientError.UsageError<E> | AgentClientError.ClientError;
13
+
14
+ export interface IAgentClient {
15
+ killIfRunning(): EffectGen<void, string>;
16
+ usingClient<A, E>(use: (client: AgentRpcClient) => EffectGen<A, E>): EffectGen<A, AgentClientError<E>>;
17
+ }
@@ -0,0 +1,133 @@
1
+ import { implementing, type EffectGen } from "effective-modules";
2
+ import { cliModules } from "..";
3
+ import { commonModules } from "@/modules/common";
4
+ import { $ } from 'bun';
5
+ import type { IGit, SetupRepoInput } from "./interface";
6
+ import { Effect, Option, pipe } from "effect";
7
+ import { existsSync } from "node:fs";
8
+ import { resolve } from "node:path";
9
+ import { SSH_KEYGEN_CMD_PATH, STANDARD_REMOTE_PREFIX } from "@/common/constants";
10
+ import { Gitmodules } from "./submodule/gitmodules";
11
+ import { readGitState } from "./submodule/git-state";
12
+ import { fixGitConfig, fixModuleDirs, fixIndexGitlinks } from "./submodule/fix";
13
+ import { printSyncState, printFixes } from "./submodule/printer";
14
+
15
+ export class GitImpl extends implementing(cliModules.Git).uses(commonModules.OSPlatform, commonModules.SSHConfig) implements IGit {
16
+ private parseOwnerRepo(remote: string): Effect.Effect<{ owner: string; repo: string }, string> {
17
+ const sshMatch = remote.match(/^git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/);
18
+ if (sshMatch) return Effect.succeed({ owner: sshMatch[1]!, repo: sshMatch[2]! });
19
+ const httpsMatch = remote.match(/^https?:\/\/[^/]+\/([^/]+)\/(.+?)(?:\.git)?$/);
20
+ if (httpsMatch) return Effect.succeed({ owner: httpsMatch[1]!, repo: httpsMatch[2]! });
21
+ return Effect.fail(`Cannot parse owner/repo from remote: ${remote}`);
22
+ }
23
+
24
+ *resolveOwnerAndRepo(maybeOwnerAndRepo: Option.Option<string>): EffectGen<{ owner: string; repo: string; }, string> {
25
+ if (Option.isSome(maybeOwnerAndRepo)) {
26
+ const parts = maybeOwnerAndRepo.value.split('/');
27
+ const [owner, repo] = parts;
28
+ if (parts.length !== 2 || !owner || !repo)
29
+ return yield* Effect.fail(`Cannot parse remote error: Expected "owner/repo", got "${maybeOwnerAndRepo.value}"`);
30
+ return { owner, repo };
31
+ }
32
+
33
+ if (!(yield* this.isGitRepository())) {
34
+ yield* Effect.fail(`not in git repo: Current directory is not a git repository`);
35
+ }
36
+
37
+ const remoteOption = yield* this.getRemoteOrigin();
38
+ if (Option.isNone(remoteOption)) {
39
+ return yield* Effect.fail(`no remote origin: No remote.origin.url — pass owner/repo as argument to clone instead`);
40
+ }
41
+
42
+ return yield* this.parseOwnerRepo(remoteOption.value);
43
+ }
44
+ *isGitRepository(): EffectGen<boolean> {
45
+ return yield* pipe(
46
+ Effect.tryPromise(async () => {
47
+ await $`git rev-parse --is-inside-work-tree`.quiet();
48
+ return true;
49
+ }),
50
+ Effect.catchTag("UnknownError", () => Effect.succeed(false))
51
+ );
52
+ }
53
+ *getRemoteOrigin(): EffectGen<Option.Option<string>> {
54
+ return yield* pipe(
55
+ Effect.tryPromise(async() => {
56
+ return Option.some((await $`git config --get remote.origin.url`.quiet()).text().trim());
57
+ }),
58
+ Effect.catchTag("UnknownError", () => Effect.succeed(Option.none()))
59
+ )
60
+ }
61
+ *clone(owner: string, repo: string): EffectGen<void, string> {
62
+ const result = Bun.spawnSync(['git', 'clone', '--recurse-submodules', `${STANDARD_REMOTE_PREFIX}${owner}/${repo}.git`], { stdout: 'inherit', stderr: 'inherit' });
63
+ if (result.exitCode !== 0)
64
+ return yield* Effect.fail(`git clone failed with exit code ${result.exitCode}`);
65
+ process.chdir(`./${repo}`);
66
+ }
67
+ *setLocalConfig(key: string, value: string): EffectGen<void, string> {
68
+ yield* Effect.tryPromise({
69
+ try: async () => { await $`git config --local ${key} ${value}`.quiet(); },
70
+ catch: (e) => `git config ${key}: ${e instanceof Error ? e.message : String(e)}`,
71
+ });
72
+ }
73
+ private *configureRepo({ dir, remoteURL, user, pubkeyPath }: SetupRepoInput): EffectGen<void, string> {
74
+ yield* Effect.log(`Configuring ${dir}`);
75
+ yield* Effect.tryPromise({
76
+ try: async () => {
77
+ const configs: [string, string][] = [
78
+ ['remote.origin.url', remoteURL],
79
+ ['user.name', user.name],
80
+ ['user.email', user.email],
81
+ ['user.signingkey', pubkeyPath],
82
+ ['gpg.format', 'ssh'],
83
+ ['gpg.ssh.program', SSH_KEYGEN_CMD_PATH],
84
+ ['commit.gpgsign', 'true'],
85
+ ['tag.gpgsign', 'true'],
86
+ ];
87
+ for (const [k, v] of configs) {
88
+ const r = Bun.spawnSync(['git', 'config', '--local', k, v], { cwd: dir, stdout: 'pipe', stderr: 'pipe' });
89
+ if (r.exitCode !== 0) {
90
+ const err = new TextDecoder().decode(r.stderr);
91
+ throw new Error(`git config ${k} failed: ${err.trim()}`);
92
+ }
93
+ }
94
+ },
95
+ catch: (e) => e instanceof Error ? e.message : String(e),
96
+ });
97
+ }
98
+ private *syncSubmoduleState(repoRoot: string, topLevelRoot: string): EffectGen<Gitmodules, string> {
99
+ const gitmodules = yield* Gitmodules.fromRepoRoot(repoRoot);
100
+ if (gitmodules.list.length === 0) return gitmodules;
101
+ const submodulePaths = gitmodules.list.map(m => m.path);
102
+ const state = yield* readGitState(repoRoot, submodulePaths);
103
+ printSyncState(gitmodules, state);
104
+ const fixResults = yield* Effect.all(
105
+ [
106
+ fixGitConfig(repoRoot, gitmodules),
107
+ fixModuleDirs(repoRoot, gitmodules, state.moduleDirs, state.orphanModuleDirs, topLevelRoot),
108
+ fixIndexGitlinks(repoRoot, gitmodules, state.index),
109
+ ],
110
+ { concurrency: "unbounded" },
111
+ );
112
+ printFixes(fixResults.flat());
113
+ return gitmodules;
114
+ }
115
+ *setupRepo(input: SetupRepoInput): EffectGen<void, string> {
116
+ yield* this.setupRepoAt(input, input.dir);
117
+ }
118
+ private *setupRepoAt(input: SetupRepoInput, topLevelRoot: string): EffectGen<void, string> {
119
+ yield* this.configureRepo(input);
120
+ if (!existsSync(resolve(input.dir, '.gitmodules'))) return;
121
+ const gitmodules = yield* this.syncSubmoduleState(input.dir, topLevelRoot);
122
+ for (const m of gitmodules.list) {
123
+ const subDir = resolve(input.dir, m.path);
124
+ if (!existsSync(subDir)) continue;
125
+ yield* this.setupRepoAt({
126
+ dir: subDir,
127
+ remoteURL: m.url,
128
+ user: input.user,
129
+ pubkeyPath: input.pubkeyPath
130
+ }, topLevelRoot);
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,23 @@
1
+ import { type EffectGen} from "effective-modules";
2
+ import { Option } from "effect";
3
+
4
+ export interface GitUser {
5
+ name: string;
6
+ email: string;
7
+ }
8
+
9
+ export interface SetupRepoInput {
10
+ dir: string;
11
+ user: GitUser;
12
+ pubkeyPath: string;
13
+ remoteURL: string;
14
+ }
15
+
16
+ export interface IGit {
17
+ isGitRepository(): EffectGen<boolean>;
18
+ getRemoteOrigin(): EffectGen<Option.Option<string>>;
19
+ resolveOwnerAndRepo(maybeOwnerAndRepo: Option.Option<string>): EffectGen<{owner: string; repo: string;}, string>;
20
+ clone(owner: string, repo: string): EffectGen<void, string>;
21
+ setLocalConfig(key: string, value: string): EffectGen<void, string>;
22
+ setupRepo(params: SetupRepoInput): EffectGen<void, string>;
23
+ }