gnosys 5.11.3 → 5.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/dist/cli.js +332 -5151
  2. package/dist/index.js +364 -235
  3. package/dist/lib/addCommand.d.ts +9 -0
  4. package/dist/lib/addCommand.js +103 -0
  5. package/dist/lib/addStructuredCommand.d.ts +16 -0
  6. package/dist/lib/addStructuredCommand.js +103 -0
  7. package/dist/lib/ambiguityCommand.d.ts +4 -0
  8. package/dist/lib/ambiguityCommand.js +36 -0
  9. package/dist/lib/apiKeyVault.d.ts +78 -0
  10. package/dist/lib/apiKeyVault.js +447 -0
  11. package/dist/lib/askCommand.d.ts +13 -0
  12. package/dist/lib/askCommand.js +145 -0
  13. package/dist/lib/audioExtract.js +4 -1
  14. package/dist/lib/auditCommand.d.ts +7 -0
  15. package/dist/lib/auditCommand.js +27 -0
  16. package/dist/lib/backupCommand.d.ts +6 -0
  17. package/dist/lib/backupCommand.js +54 -0
  18. package/dist/lib/bootstrapCommand.d.ts +15 -0
  19. package/dist/lib/bootstrapCommand.js +51 -0
  20. package/dist/lib/briefingCommand.d.ts +7 -0
  21. package/dist/lib/briefingCommand.js +92 -0
  22. package/dist/lib/centralizeCommand.d.ts +5 -0
  23. package/dist/lib/centralizeCommand.js +16 -0
  24. package/dist/lib/chatCommand.d.ts +12 -0
  25. package/dist/lib/chatCommand.js +46 -0
  26. package/dist/lib/checkCommand.d.ts +4 -0
  27. package/dist/lib/checkCommand.js +133 -0
  28. package/dist/lib/clientReadOverlay.d.ts +27 -0
  29. package/dist/lib/clientReadOverlay.js +73 -0
  30. package/dist/lib/clientReadResolve.d.ts +32 -0
  31. package/dist/lib/clientReadResolve.js +84 -0
  32. package/dist/lib/commitContextCommand.d.ts +9 -0
  33. package/dist/lib/commitContextCommand.js +142 -0
  34. package/dist/lib/config.d.ts +43 -3
  35. package/dist/lib/config.js +58 -57
  36. package/dist/lib/configCommand.d.ts +10 -0
  37. package/dist/lib/configCommand.js +321 -0
  38. package/dist/lib/connectCommand.d.ts +8 -0
  39. package/dist/lib/connectCommand.js +19 -0
  40. package/dist/lib/db.d.ts +52 -0
  41. package/dist/lib/db.js +169 -1
  42. package/dist/lib/dearchiveCommand.d.ts +7 -0
  43. package/dist/lib/dearchiveCommand.js +41 -0
  44. package/dist/lib/discoverCommand.d.ts +9 -0
  45. package/dist/lib/discoverCommand.js +87 -0
  46. package/dist/lib/doctorCommand.d.ts +6 -0
  47. package/dist/lib/doctorCommand.js +256 -0
  48. package/dist/lib/dream.d.ts +42 -2
  49. package/dist/lib/dream.js +290 -30
  50. package/dist/lib/dreamCommand.d.ts +10 -0
  51. package/dist/lib/dreamCommand.js +195 -0
  52. package/dist/lib/dreamLaunchd.d.ts +2 -0
  53. package/dist/lib/dreamLaunchd.js +72 -0
  54. package/dist/lib/dreamLogCommand.d.ts +10 -0
  55. package/dist/lib/dreamLogCommand.js +58 -0
  56. package/dist/lib/dreamReport.d.ts +7 -0
  57. package/dist/lib/dreamReport.js +114 -0
  58. package/dist/lib/dreamRunLog.d.ts +121 -0
  59. package/dist/lib/dreamRunLog.js +212 -0
  60. package/dist/lib/embeddings.js +3 -0
  61. package/dist/lib/exportCommand.d.ts +18 -0
  62. package/dist/lib/exportCommand.js +101 -0
  63. package/dist/lib/fsearchCommand.d.ts +8 -0
  64. package/dist/lib/fsearchCommand.js +44 -0
  65. package/dist/lib/graphCommand.d.ts +4 -0
  66. package/dist/lib/graphCommand.js +68 -0
  67. package/dist/lib/helperGenerateCommand.d.ts +5 -0
  68. package/dist/lib/helperGenerateCommand.js +27 -0
  69. package/dist/lib/historyCommand.d.ts +5 -0
  70. package/dist/lib/historyCommand.js +51 -0
  71. package/dist/lib/hybridSearchCommand.d.ts +12 -0
  72. package/dist/lib/hybridSearchCommand.js +95 -0
  73. package/dist/lib/ideMcpInstall.d.ts +30 -0
  74. package/dist/lib/ideMcpInstall.js +102 -0
  75. package/dist/lib/importCommand.d.ts +16 -0
  76. package/dist/lib/importCommand.js +89 -0
  77. package/dist/lib/importProjectCommand.d.ts +6 -0
  78. package/dist/lib/importProjectCommand.js +43 -0
  79. package/dist/lib/ingestCommand.d.ts +13 -0
  80. package/dist/lib/ingestCommand.js +95 -0
  81. package/dist/lib/installOutput.d.ts +36 -0
  82. package/dist/lib/installOutput.js +55 -0
  83. package/dist/lib/lensCommand.d.ts +20 -0
  84. package/dist/lib/lensCommand.js +61 -0
  85. package/dist/lib/lensing.d.ts +1 -0
  86. package/dist/lib/lensing.js +50 -9
  87. package/dist/lib/linksCommand.d.ts +7 -0
  88. package/dist/lib/linksCommand.js +48 -0
  89. package/dist/lib/listCommand.d.ts +8 -0
  90. package/dist/lib/listCommand.js +74 -0
  91. package/dist/lib/llm.d.ts +1 -1
  92. package/dist/lib/llm.js +26 -8
  93. package/dist/lib/localDiskCheck.d.ts +17 -0
  94. package/dist/lib/localDiskCheck.js +54 -0
  95. package/dist/lib/machineConfig.d.ts +11 -1
  96. package/dist/lib/machineConfig.js +16 -0
  97. package/dist/lib/machineRegistry.d.ts +61 -0
  98. package/dist/lib/machineRegistry.js +80 -0
  99. package/dist/lib/maintainCommand.d.ts +8 -0
  100. package/dist/lib/maintainCommand.js +34 -0
  101. package/dist/lib/masterLease.d.ts +20 -0
  102. package/dist/lib/masterLease.js +68 -0
  103. package/dist/lib/migrateCommand.d.ts +7 -0
  104. package/dist/lib/migrateCommand.js +158 -0
  105. package/dist/lib/migrateDbCommand.d.ts +9 -0
  106. package/dist/lib/migrateDbCommand.js +94 -0
  107. package/dist/lib/modelValidation.d.ts +5 -0
  108. package/dist/lib/modelValidation.js +27 -0
  109. package/dist/lib/openrouterTiers.d.ts +29 -0
  110. package/dist/lib/openrouterTiers.js +113 -0
  111. package/dist/lib/prefCommand.d.ts +10 -0
  112. package/dist/lib/prefCommand.js +118 -0
  113. package/dist/lib/projectsCommand.d.ts +8 -0
  114. package/dist/lib/projectsCommand.js +131 -0
  115. package/dist/lib/readCommand.d.ts +7 -0
  116. package/dist/lib/readCommand.js +62 -0
  117. package/dist/lib/recall.d.ts +3 -0
  118. package/dist/lib/recall.js +19 -4
  119. package/dist/lib/recallCommand.d.ts +11 -0
  120. package/dist/lib/recallCommand.js +112 -0
  121. package/dist/lib/reflectCommand.d.ts +8 -0
  122. package/dist/lib/reflectCommand.js +61 -0
  123. package/dist/lib/reindexCommand.d.ts +4 -0
  124. package/dist/lib/reindexCommand.js +34 -0
  125. package/dist/lib/reindexGraphCommand.d.ts +4 -0
  126. package/dist/lib/reindexGraphCommand.js +12 -0
  127. package/dist/lib/reinforceCommand.d.ts +8 -0
  128. package/dist/lib/reinforceCommand.js +40 -0
  129. package/dist/lib/remote.d.ts +5 -1
  130. package/dist/lib/remote.js +5 -1
  131. package/dist/lib/remoteWizard.d.ts +24 -5
  132. package/dist/lib/remoteWizard.js +308 -319
  133. package/dist/lib/restoreCommand.d.ts +5 -0
  134. package/dist/lib/restoreCommand.js +35 -0
  135. package/dist/lib/sandboxStartCommand.d.ts +6 -0
  136. package/dist/lib/sandboxStartCommand.js +25 -0
  137. package/dist/lib/sandboxStatusCommand.d.ts +4 -0
  138. package/dist/lib/sandboxStatusCommand.js +24 -0
  139. package/dist/lib/sandboxStopCommand.d.ts +4 -0
  140. package/dist/lib/sandboxStopCommand.js +21 -0
  141. package/dist/lib/searchCommand.d.ts +9 -0
  142. package/dist/lib/searchCommand.js +90 -0
  143. package/dist/lib/semanticSearchCommand.d.ts +8 -0
  144. package/dist/lib/semanticSearchCommand.js +52 -0
  145. package/dist/lib/setup/configSetRender.js +2 -0
  146. package/dist/lib/setup/providerGlyphs.d.ts +19 -0
  147. package/dist/lib/setup/providerGlyphs.js +42 -0
  148. package/dist/lib/setup/remoteRender.d.ts +31 -1
  149. package/dist/lib/setup/remoteRender.js +95 -4
  150. package/dist/lib/setup/sections/ides.d.ts +7 -0
  151. package/dist/lib/setup/sections/ides.js +24 -2
  152. package/dist/lib/setup/sections/providers.d.ts +17 -0
  153. package/dist/lib/setup/sections/providers.js +255 -0
  154. package/dist/lib/setup/sections/routing.d.ts +2 -6
  155. package/dist/lib/setup/sections/routing.js +33 -85
  156. package/dist/lib/setup/sections/taskRoutingEditor.d.ts +17 -0
  157. package/dist/lib/setup/sections/taskRoutingEditor.js +149 -0
  158. package/dist/lib/setup/summary.d.ts +9 -0
  159. package/dist/lib/setup/summary.js +51 -37
  160. package/dist/lib/setup/ui/status.d.ts +1 -0
  161. package/dist/lib/setup/ui/status.js +2 -0
  162. package/dist/lib/setup.d.ts +108 -3
  163. package/dist/lib/setup.js +813 -303
  164. package/dist/lib/setupKeys.d.ts +42 -0
  165. package/dist/lib/setupKeys.js +564 -0
  166. package/dist/lib/setupRemoteCommand.d.ts +4 -0
  167. package/dist/lib/setupRemoteCommand.js +28 -0
  168. package/dist/lib/setupRemotePullCommand.d.ts +5 -0
  169. package/dist/lib/setupRemotePullCommand.js +52 -0
  170. package/dist/lib/setupRemotePushCommand.d.ts +5 -0
  171. package/dist/lib/setupRemotePushCommand.js +57 -0
  172. package/dist/lib/setupRemoteResolveCommand.d.ts +4 -0
  173. package/dist/lib/setupRemoteResolveCommand.js +48 -0
  174. package/dist/lib/setupRemoteStatusCommand.d.ts +4 -0
  175. package/dist/lib/setupRemoteStatusCommand.js +73 -0
  176. package/dist/lib/setupRemoteSyncCommand.d.ts +6 -0
  177. package/dist/lib/setupRemoteSyncCommand.js +65 -0
  178. package/dist/lib/setupSyncProjectsCommand.d.ts +4 -0
  179. package/dist/lib/setupSyncProjectsCommand.js +292 -0
  180. package/dist/lib/staleCommand.d.ts +8 -0
  181. package/dist/lib/staleCommand.js +34 -0
  182. package/dist/lib/statsCommand.d.ts +6 -0
  183. package/dist/lib/statsCommand.js +142 -0
  184. package/dist/lib/statusCommand.d.ts +18 -0
  185. package/dist/lib/statusCommand.js +250 -0
  186. package/dist/lib/storesCommand.d.ts +2 -0
  187. package/dist/lib/storesCommand.js +4 -0
  188. package/dist/lib/syncClient.d.ts +47 -0
  189. package/dist/lib/syncClient.js +212 -0
  190. package/dist/lib/syncCommand.d.ts +6 -0
  191. package/dist/lib/syncCommand.js +57 -0
  192. package/dist/lib/syncDoctorCommand.d.ts +5 -0
  193. package/dist/lib/syncDoctorCommand.js +100 -0
  194. package/dist/lib/syncIngest.d.ts +19 -0
  195. package/dist/lib/syncIngest.js +152 -0
  196. package/dist/lib/syncIngestLaunchd.d.ts +8 -0
  197. package/dist/lib/syncIngestLaunchd.js +93 -0
  198. package/dist/lib/syncIngestStartup.d.ts +5 -0
  199. package/dist/lib/syncIngestStartup.js +29 -0
  200. package/dist/lib/syncIngestSystemd.d.ts +10 -0
  201. package/dist/lib/syncIngestSystemd.js +97 -0
  202. package/dist/lib/syncIngestTimer.d.ts +8 -0
  203. package/dist/lib/syncIngestTimer.js +27 -0
  204. package/dist/lib/syncIngestTimerCommand.d.ts +7 -0
  205. package/dist/lib/syncIngestTimerCommand.js +83 -0
  206. package/dist/lib/syncLock.d.ts +6 -0
  207. package/dist/lib/syncLock.js +74 -0
  208. package/dist/lib/syncSnapshot.d.ts +30 -0
  209. package/dist/lib/syncSnapshot.js +184 -0
  210. package/dist/lib/syncStaging.d.ts +81 -0
  211. package/dist/lib/syncStaging.js +239 -0
  212. package/dist/lib/tagsAddCommand.d.ts +8 -0
  213. package/dist/lib/tagsAddCommand.js +18 -0
  214. package/dist/lib/tagsCommand.d.ts +4 -0
  215. package/dist/lib/tagsCommand.js +16 -0
  216. package/dist/lib/timelineCommand.d.ts +7 -0
  217. package/dist/lib/timelineCommand.js +49 -0
  218. package/dist/lib/traceCommand.d.ts +6 -0
  219. package/dist/lib/traceCommand.js +39 -0
  220. package/dist/lib/traverseCommand.d.ts +6 -0
  221. package/dist/lib/traverseCommand.js +58 -0
  222. package/dist/lib/updateCommand.d.ts +13 -0
  223. package/dist/lib/updateCommand.js +67 -0
  224. package/dist/lib/updateStatusCommand.d.ts +5 -0
  225. package/dist/lib/updateStatusCommand.js +38 -0
  226. package/dist/lib/webAddCommand.d.ts +8 -0
  227. package/dist/lib/webAddCommand.js +55 -0
  228. package/dist/lib/webBuildCommand.d.ts +10 -0
  229. package/dist/lib/webBuildCommand.js +65 -0
  230. package/dist/lib/webBuildIndexCommand.d.ts +8 -0
  231. package/dist/lib/webBuildIndexCommand.js +37 -0
  232. package/dist/lib/webIngestCommand.d.ts +11 -0
  233. package/dist/lib/webIngestCommand.js +51 -0
  234. package/dist/lib/webInitCommand.d.ts +9 -0
  235. package/dist/lib/webInitCommand.js +167 -0
  236. package/dist/lib/webRemoveCommand.d.ts +5 -0
  237. package/dist/lib/webRemoveCommand.js +41 -0
  238. package/dist/lib/webStatusCommand.d.ts +5 -0
  239. package/dist/lib/webStatusCommand.js +94 -0
  240. package/dist/lib/webUpdateCommand.d.ts +7 -0
  241. package/dist/lib/webUpdateCommand.js +72 -0
  242. package/dist/lib/workingSetCommand.d.ts +6 -0
  243. package/dist/lib/workingSetCommand.js +37 -0
  244. package/package.json +2 -1
@@ -0,0 +1,42 @@
1
+ import { type Interface as ReadlineInterface } from "readline/promises";
2
+ export interface KeysSetupOpts {
3
+ rl?: ReadlineInterface;
4
+ }
5
+ export type KnownKeyProvider = "anthropic" | "openrouter" | "openai" | "xai" | "google" | "cohere" | "mistral" | "groq" | "ollama" | "lmstudio" | "custom";
6
+ export interface ProviderKeyStatus {
7
+ provider: KnownKeyProvider;
8
+ found: boolean;
9
+ location: "keychain" | "env" | "dotenv" | "none";
10
+ envVarName?: string;
11
+ serviceName?: string;
12
+ lastFour?: string;
13
+ }
14
+ type KeyLocationKind = "keychain" | "env" | "dotenv";
15
+ interface StoredKeyLocation {
16
+ location: KeyLocationKind;
17
+ label: string;
18
+ value: string;
19
+ envVarName?: string;
20
+ serviceName?: string;
21
+ deletable: boolean;
22
+ }
23
+ declare function removeDotenvKeys(envVarNames: string[]): Promise<number>;
24
+ declare function listKeyLocations(provider: KnownKeyProvider): StoredKeyLocation[];
25
+ declare function validateProviderKey(provider: KnownKeyProvider, key: string): Promise<boolean>;
26
+ declare function chooseKeyDestination(rl: ReadlineInterface, provider: KnownKeyProvider, key: string): Promise<void>;
27
+ declare function copyToKeychain(rl: ReadlineInterface, provider: KnownKeyProvider): Promise<void>;
28
+ declare function updateKey(rl: ReadlineInterface, provider: KnownKeyProvider): Promise<void>;
29
+ declare function deleteKey(rl: ReadlineInterface, provider: KnownKeyProvider): Promise<"list" | "detail">;
30
+ export declare function listProviders(): Promise<ProviderKeyStatus[]>;
31
+ export declare function renderProviderTable(providers: ProviderKeyStatus[]): string;
32
+ export declare function runKeysSetup(opts?: KeysSetupOpts): Promise<void>;
33
+ export declare const setupKeysTestHooks: {
34
+ chooseKeyDestination: typeof chooseKeyDestination;
35
+ copyToKeychain: typeof copyToKeychain;
36
+ deleteKey: typeof deleteKey;
37
+ listKeyLocations: typeof listKeyLocations;
38
+ removeDotenvKeys: typeof removeDotenvKeys;
39
+ updateKey: typeof updateKey;
40
+ validateProviderKey: typeof validateProviderKey;
41
+ };
42
+ export {};
@@ -0,0 +1,564 @@
1
+ import { createInterface } from "readline/promises";
2
+ import { stdin, stdout } from "process";
3
+ import fs from "fs/promises";
4
+ import fsSync from "fs";
5
+ import os from "os";
6
+ import path from "path";
7
+ import dotenv from "dotenv";
8
+ import { deleteStoredSecret, detectKeyLocation, providerNeedsApiKey, readSecureStoreSecret, storeApiKeySecret, } from "./apiKeyVault.js";
9
+ import { askInput, askPassword, askYesNo, printInfo, printStatus, writeApiKey, } from "./setup.js";
10
+ import { validateModel } from "./modelValidation.js";
11
+ const PROVIDERS = [
12
+ "anthropic",
13
+ "openrouter",
14
+ "openai",
15
+ "xai",
16
+ "google",
17
+ "cohere",
18
+ "mistral",
19
+ "groq",
20
+ "ollama",
21
+ "lmstudio",
22
+ "custom",
23
+ ];
24
+ const LOCAL_PROVIDERS = new Set(["ollama", "lmstudio"]);
25
+ const TABLE_RULE = "═══════════════════════════════════════════════════════════════";
26
+ const TABLE_SEPARATOR = "──────────────────────────────────────────────────────────────";
27
+ const RESET = "\x1b[0m";
28
+ const GREEN = "\x1b[32m";
29
+ const RED = "\x1b[31m";
30
+ const DEFAULT_MODELS = {
31
+ anthropic: "claude-haiku-4-5",
32
+ openrouter: "nvidia/nemotron-3-super-120b-a12b:free",
33
+ openai: "gpt-5.4-mini",
34
+ xai: "grok-4.20",
35
+ google: "gemini-2.5-flash",
36
+ cohere: "command-r-plus",
37
+ mistral: "mistral-small-4",
38
+ groq: "llama-3.3-70b-versatile",
39
+ ollama: "llama3.2",
40
+ lmstudio: "default",
41
+ custom: "",
42
+ };
43
+ const VALIDATION_ENDPOINTS = {
44
+ anthropic: "/v1/messages",
45
+ openrouter: "/api/v1/chat/completions",
46
+ openai: "/v1/chat/completions",
47
+ xai: "/v1/chat/completions",
48
+ mistral: "/v1/chat/completions",
49
+ groq: "/openai/v1/chat/completions",
50
+ ollama: "/api/chat",
51
+ lmstudio: "/v1/chat/completions",
52
+ };
53
+ const HTTP_STATUS_TEXT = {
54
+ 400: "Bad Request",
55
+ 401: "Unauthorized",
56
+ 403: "Forbidden",
57
+ 404: "Not Found",
58
+ 408: "Request Timeout",
59
+ 429: "Too Many Requests",
60
+ 500: "Internal Server Error",
61
+ 502: "Bad Gateway",
62
+ 503: "Service Unavailable",
63
+ };
64
+ function supportsColor() {
65
+ return Boolean(process.stdout.isTTY && !process.env.NO_COLOR);
66
+ }
67
+ function color(token, text) {
68
+ return supportsColor() ? `${token}${text}${RESET}` : text;
69
+ }
70
+ function pad(value, width) {
71
+ return value.padEnd(width, " ");
72
+ }
73
+ function statusLabel(provider) {
74
+ if (LOCAL_PROVIDERS.has(provider.provider)) {
75
+ return "N/A (local)";
76
+ }
77
+ if (provider.found) {
78
+ return color(GREEN, "✓ Present");
79
+ }
80
+ return color(RED, "✗ Missing");
81
+ }
82
+ function locationLabel(provider) {
83
+ switch (provider.location) {
84
+ case "keychain":
85
+ return "Keychain";
86
+ case "dotenv":
87
+ return ".env";
88
+ case "env":
89
+ return provider.envVarName ? `Env Var (${provider.envVarName})` : "Env Var";
90
+ case "none":
91
+ return "—";
92
+ }
93
+ }
94
+ function providerSlug(provider) {
95
+ return provider.toUpperCase();
96
+ }
97
+ function globalEnvVar(provider) {
98
+ return `GNOSYS_GLOBAL_${providerSlug(provider)}_KEY`;
99
+ }
100
+ function providerEnvVar(provider) {
101
+ return `GNOSYS_${providerSlug(provider)}_KEY`;
102
+ }
103
+ function legacyEnvVar(provider) {
104
+ return `${providerSlug(provider)}_API_KEY`;
105
+ }
106
+ function gnosysEnvPath() {
107
+ return path.join(process.env.HOME ?? os.homedir(), ".config", "gnosys", ".env");
108
+ }
109
+ function keyLookupEntries(provider) {
110
+ const globalName = globalEnvVar(provider);
111
+ const providerName = providerEnvVar(provider);
112
+ return [
113
+ { envVarName: globalName, serviceName: globalName },
114
+ { envVarName: providerName, serviceName: providerName },
115
+ { envVarName: legacyEnvVar(provider) },
116
+ { envVarName: "GNOSYS_LLM_API_KEY" },
117
+ ];
118
+ }
119
+ function readDotenvKeys() {
120
+ try {
121
+ return dotenv.parse(fsSync.readFileSync(gnosysEnvPath(), "utf-8"));
122
+ }
123
+ catch {
124
+ return {};
125
+ }
126
+ }
127
+ async function removeDotenvKeys(envVarNames) {
128
+ const envPath = gnosysEnvPath();
129
+ let content = "";
130
+ try {
131
+ content = await fs.readFile(envPath, "utf-8");
132
+ }
133
+ catch {
134
+ return 0;
135
+ }
136
+ const names = new Set(envVarNames);
137
+ let removed = 0;
138
+ const lines = content.split("\n").filter((line) => {
139
+ const key = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1];
140
+ if (key && names.has(key)) {
141
+ removed++;
142
+ return false;
143
+ }
144
+ return true;
145
+ });
146
+ await fs.writeFile(envPath, lines.join("\n").replace(/\n*$/, "\n"), "utf-8");
147
+ await fs.chmod(envPath, 0o600);
148
+ return removed;
149
+ }
150
+ function keyPreview(key) {
151
+ const trimmed = key?.trim();
152
+ if (!trimmed)
153
+ return "—";
154
+ if (trimmed.length <= 8)
155
+ return `${trimmed.slice(0, 2)}••••${trimmed.slice(-2)} (last 2)`;
156
+ return `${trimmed.slice(0, 8)}-••••••••••••••••••••••••••••••••••${trimmed.slice(-4)} (last 4)`;
157
+ }
158
+ function listKeyLocations(provider) {
159
+ if (!providerNeedsApiKey(provider))
160
+ return [];
161
+ const dotenvKeys = readDotenvKeys();
162
+ const locations = [];
163
+ for (const { envVarName, serviceName } of keyLookupEntries(provider)) {
164
+ const envValue = process.env[envVarName]?.trim();
165
+ if (envValue) {
166
+ locations.push({
167
+ location: "env",
168
+ label: `Env Var (${envVarName})`,
169
+ value: envValue,
170
+ envVarName,
171
+ deletable: false,
172
+ });
173
+ }
174
+ if (serviceName) {
175
+ const secret = readSecureStoreSecret(serviceName)?.trim();
176
+ if (secret) {
177
+ locations.push({
178
+ location: "keychain",
179
+ label: "Keychain",
180
+ value: secret,
181
+ serviceName,
182
+ deletable: true,
183
+ });
184
+ }
185
+ }
186
+ const dotenvValue = dotenvKeys[envVarName]?.trim();
187
+ if (dotenvValue) {
188
+ locations.push({
189
+ location: "dotenv",
190
+ label: ".env",
191
+ value: dotenvValue,
192
+ envVarName,
193
+ deletable: true,
194
+ });
195
+ }
196
+ }
197
+ return locations;
198
+ }
199
+ function currentKeyLocation(provider) {
200
+ const detected = detectKeyLocation(provider);
201
+ if (!detected.found)
202
+ return undefined;
203
+ return listKeyLocations(provider).find((location) => {
204
+ if (detected.location === "keychain") {
205
+ return location.location === "keychain" && location.serviceName === detected.serviceName;
206
+ }
207
+ if (detected.location === "dotenv") {
208
+ return location.location === "dotenv" && location.envVarName === detected.envVarName;
209
+ }
210
+ if (detected.location === "env") {
211
+ return location.location === "env" && location.envVarName === detected.envVarName;
212
+ }
213
+ return false;
214
+ });
215
+ }
216
+ function renderProviderDetail(provider) {
217
+ const detected = detectKeyLocation(provider);
218
+ const current = currentKeyLocation(provider);
219
+ const locations = listKeyLocations(provider);
220
+ const extraLocations = locations.filter((location) => location !== current);
221
+ const lines = [
222
+ TABLE_RULE,
223
+ ` ${provider.toUpperCase()}`,
224
+ TABLE_RULE,
225
+ "",
226
+ "Current key:",
227
+ ];
228
+ if (LOCAL_PROVIDERS.has(provider)) {
229
+ lines.push(" Status: N/A (local provider)", " Stored in: —", " Value: —", "", "Actions:", " [t] Test local provider", " [b] Back to list", "");
230
+ return lines.join("\n");
231
+ }
232
+ lines.push(` Status: ${detected.found ? color(GREEN, "✓ Present") : color(RED, "✗ Missing")}`, ` Stored in: ${detected.found ? locationLabel({ provider, ...detected }) : "—"}`, ` Value: ${keyPreview(current?.value)}`);
233
+ if (extraLocations.length > 0) {
234
+ lines.push("", "Also found:");
235
+ for (const location of extraLocations) {
236
+ lines.push(` - ${location.label}: ${keyPreview(location.value)}`);
237
+ }
238
+ }
239
+ lines.push("", "Actions:", " [c] Copy to Keychain (recommended)", " [u] Update key", " [d] Delete key", " [t] Test/validate key", " [b] Back to list", "");
240
+ return lines.join("\n");
241
+ }
242
+ async function validateProviderKey(provider, key) {
243
+ const model = DEFAULT_MODELS[provider];
244
+ if (!model) {
245
+ printStatus("warn", `validation is not configured for ${provider}`);
246
+ return false;
247
+ }
248
+ const result = await validateModel(provider, model, key);
249
+ if (result.ok) {
250
+ console.log();
251
+ console.log("✓ Key is valid");
252
+ console.log(`Provider: ${provider}`);
253
+ console.log(`Test API: ${VALIDATION_ENDPOINTS[provider] ?? "/"} (HTTP 200)`);
254
+ return true;
255
+ }
256
+ console.log();
257
+ console.log("✗ Key validation failed");
258
+ console.log(`Provider: ${provider}`);
259
+ console.log(`Error: ${formatValidationError(result.error)}`);
260
+ return false;
261
+ }
262
+ function formatValidationError(error) {
263
+ if (!error)
264
+ return "Unknown validation error";
265
+ const match = error.match(/^HTTP\s+(\d+)(?::\s*)?(.*)$/i);
266
+ if (!match)
267
+ return error;
268
+ const code = Number.parseInt(match[1], 10);
269
+ const message = match[2]?.trim();
270
+ const status = HTTP_STATUS_TEXT[code] ?? "HTTP Error";
271
+ return message ? `${code} ${status} - ${message}` : `${code} ${status}`;
272
+ }
273
+ async function chooseKeyDestination(rl, provider, key) {
274
+ console.log();
275
+ console.log("Where should I store this key?");
276
+ console.log();
277
+ console.log(" 1. OS Keychain (macOS/Linux/Windows secure store) [recommended]");
278
+ console.log(" 2. Config file (~/.config/gnosys/.env)");
279
+ console.log(" 3. Don't store — I'll set it as an environment variable myself");
280
+ console.log();
281
+ const choice = (await askInput(rl, "Select", { default: "1" })).trim();
282
+ if (choice === "2") {
283
+ await writeApiKey(provider, key, { scope: "global" });
284
+ printStatus("ok", "key saved", `~/.config/gnosys/.env · ${globalEnvVar(provider)}`);
285
+ return;
286
+ }
287
+ if (choice === "3") {
288
+ printInfo(`Set ${globalEnvVar(provider)} in your shell environment.`);
289
+ printInfo("No key was stored by Gnosys.");
290
+ return;
291
+ }
292
+ const service = globalEnvVar(provider);
293
+ if (storeApiKeySecret(service, key, provider)) {
294
+ printStatus("ok", "key saved to secure store", service);
295
+ }
296
+ else {
297
+ printStatus("fail", "could not write to secure store");
298
+ }
299
+ }
300
+ async function copyToKeychain(rl, provider) {
301
+ const current = currentKeyLocation(provider);
302
+ if (!current) {
303
+ printStatus("warn", `no key found for ${provider}`);
304
+ return;
305
+ }
306
+ if (current.location === "keychain") {
307
+ printStatus("ok", "key is already in Keychain");
308
+ return;
309
+ }
310
+ if (!(await validateProviderKey(provider, current.value))) {
311
+ printStatus("warn", "copy cancelled because validation failed");
312
+ return;
313
+ }
314
+ console.log();
315
+ console.log("Copy this key to Keychain (recommended)?");
316
+ console.log();
317
+ console.log("This will:");
318
+ console.log("✓ Store the key securely in macOS Keychain (encrypted)");
319
+ if (current.location === "dotenv") {
320
+ console.log("✓ Remove the key from ~/.config/gnosys/.env");
321
+ }
322
+ console.log();
323
+ console.log("✗ WARNING: Environment variables are checked before Keychain.");
324
+ console.log(` Manually unset ${globalEnvVar(provider)}, ${providerEnvVar(provider)},`);
325
+ console.log(` and ${legacyEnvVar(provider)} if they exist in your shell.`);
326
+ console.log();
327
+ if (!(await askYesNo(rl, "Continue?", false))) {
328
+ printStatus("warn", "copy cancelled");
329
+ return;
330
+ }
331
+ const service = globalEnvVar(provider);
332
+ if (!storeApiKeySecret(service, current.value, provider)) {
333
+ printStatus("fail", "could not write to secure store");
334
+ return;
335
+ }
336
+ if (current.location === "dotenv" && current.envVarName) {
337
+ await removeDotenvKeys([current.envVarName]);
338
+ }
339
+ printStatus("ok", "copied to Keychain", service);
340
+ if (current.location === "env" && current.envVarName) {
341
+ printStatus("warn", `manually unset ${current.envVarName}`, "environment variables cannot be deleted here");
342
+ }
343
+ }
344
+ async function updateKey(rl, provider) {
345
+ const key = await askPassword(rl, `Enter your ${provider} API key`);
346
+ if (!key) {
347
+ printStatus("warn", "no key entered");
348
+ return;
349
+ }
350
+ if (!(await validateProviderKey(provider, key))) {
351
+ printStatus("warn", "key not stored because validation failed");
352
+ return;
353
+ }
354
+ await chooseKeyDestination(rl, provider, key);
355
+ }
356
+ async function deleteKey(rl, provider) {
357
+ const current = currentKeyLocation(provider);
358
+ const locations = listKeyLocations(provider);
359
+ if (!current) {
360
+ printStatus("warn", `no key found for ${provider}`);
361
+ return "detail";
362
+ }
363
+ console.log();
364
+ console.log(`Delete the ${provider} key?`);
365
+ console.log();
366
+ console.log(`Current location: ${current.label}`);
367
+ console.log();
368
+ console.log("This will remove the key from storage.");
369
+ console.log("You can always add it back later.");
370
+ console.log();
371
+ if (!(await askYesNo(rl, "Delete?", false))) {
372
+ printStatus("warn", "delete cancelled");
373
+ return "detail";
374
+ }
375
+ let targets = [current];
376
+ if (locations.length > 1 && await askYesNo(rl, "Delete all stored copies that Gnosys can remove?", false)) {
377
+ targets = locations;
378
+ }
379
+ let removed = 0;
380
+ const dotenvNames = [];
381
+ for (const target of targets) {
382
+ if (target.location === "dotenv" && target.envVarName) {
383
+ dotenvNames.push(target.envVarName);
384
+ continue;
385
+ }
386
+ if (target.location === "keychain" && target.serviceName && deleteStoredSecret(target.serviceName)) {
387
+ removed++;
388
+ continue;
389
+ }
390
+ if (target.location === "env" && target.envVarName) {
391
+ printStatus("warn", `manually unset ${target.envVarName}`, "environment variables cannot be deleted here");
392
+ }
393
+ }
394
+ if (dotenvNames.length > 0) {
395
+ removed += await removeDotenvKeys(dotenvNames);
396
+ }
397
+ if (removed > 0) {
398
+ printStatus("ok", `deleted ${removed} stored key${removed === 1 ? "" : "s"}`);
399
+ }
400
+ else {
401
+ printStatus("warn", "no deletable key was removed");
402
+ }
403
+ return "list";
404
+ }
405
+ async function testKey(provider) {
406
+ const current = currentKeyLocation(provider);
407
+ if (LOCAL_PROVIDERS.has(provider)) {
408
+ await validateProviderKey(provider, "");
409
+ return;
410
+ }
411
+ if (!current) {
412
+ printStatus("warn", `no key found for ${provider}`);
413
+ return;
414
+ }
415
+ await validateProviderKey(provider, current.value);
416
+ }
417
+ async function showProviderDetail(rl, provider) {
418
+ while (true) {
419
+ console.log();
420
+ console.log(renderProviderDetail(provider));
421
+ const selection = (await askInput(rl, "Select")).trim().toLowerCase();
422
+ if (selection === "b" || selection === "")
423
+ return;
424
+ if (LOCAL_PROVIDERS.has(provider)) {
425
+ if (selection === "t") {
426
+ await testKey(provider);
427
+ }
428
+ else {
429
+ printStatus("warn", "local providers only support test or back");
430
+ }
431
+ continue;
432
+ }
433
+ if (selection === "c") {
434
+ await copyToKeychain(rl, provider);
435
+ continue;
436
+ }
437
+ if (selection === "u") {
438
+ await updateKey(rl, provider);
439
+ continue;
440
+ }
441
+ if (selection === "d") {
442
+ if ((await deleteKey(rl, provider)) === "list")
443
+ return;
444
+ continue;
445
+ }
446
+ if (selection === "t") {
447
+ await testKey(provider);
448
+ continue;
449
+ }
450
+ printStatus("warn", "enter c, u, d, t, or b");
451
+ }
452
+ }
453
+ async function addProvider(rl) {
454
+ const providers = await listProviders();
455
+ const candidates = providers.filter((provider) => {
456
+ if (LOCAL_PROVIDERS.has(provider.provider))
457
+ return false;
458
+ if (provider.provider === "custom")
459
+ return true;
460
+ return !provider.found;
461
+ });
462
+ if (candidates.length === 0) {
463
+ printStatus("ok", "all known cloud providers already have keys");
464
+ return;
465
+ }
466
+ console.log();
467
+ console.log("Providers without keys:");
468
+ candidates.forEach((provider, index) => {
469
+ console.log(` ${index + 1}. ${provider.provider}`);
470
+ });
471
+ console.log(" custom. Type any custom provider name to use the custom provider slot");
472
+ console.log();
473
+ const answer = (await askInput(rl, "Select provider")).trim().toLowerCase();
474
+ const selectedIndex = Number.parseInt(answer, 10);
475
+ let provider = Number.isInteger(selectedIndex)
476
+ ? candidates[selectedIndex - 1]?.provider
477
+ : candidates.find((candidate) => candidate.provider === answer)?.provider;
478
+ if (!provider && answer) {
479
+ provider = "custom";
480
+ if (answer !== "custom") {
481
+ printInfo(`Using the custom provider slot for "${answer}".`);
482
+ }
483
+ }
484
+ if (!provider) {
485
+ printStatus("warn", "provider not selected");
486
+ return;
487
+ }
488
+ await updateKey(rl, provider);
489
+ }
490
+ export async function listProviders() {
491
+ return PROVIDERS.map((provider) => ({
492
+ provider,
493
+ ...detectKeyLocation(provider),
494
+ }));
495
+ }
496
+ export function renderProviderTable(providers) {
497
+ const actionRange = providers.length > 0 ? `1-${providers.length}` : "1-N";
498
+ const rows = providers.map((provider, index) => {
499
+ const num = String(index + 1).padStart(2, " ");
500
+ return [
501
+ ` ${num}`,
502
+ pad(provider.provider, 11),
503
+ pad(statusLabel(provider), 11),
504
+ pad(locationLabel(provider), 20),
505
+ ].join(" ");
506
+ });
507
+ return [
508
+ TABLE_RULE,
509
+ " GNOSYS PROVIDER KEYS",
510
+ TABLE_RULE,
511
+ "",
512
+ " # Provider Key Status Stored In",
513
+ TABLE_SEPARATOR,
514
+ ...rows,
515
+ "",
516
+ "Actions:",
517
+ ` [${actionRange}] Select provider`,
518
+ " [a] Add new provider",
519
+ " [q] Quit",
520
+ "",
521
+ ].join("\n");
522
+ }
523
+ export async function runKeysSetup(opts) {
524
+ const ownReadline = !opts?.rl;
525
+ const rl = opts?.rl ?? createInterface({ input: stdin, output: stdout });
526
+ try {
527
+ while (true) {
528
+ const providers = await listProviders();
529
+ console.log();
530
+ console.log(renderProviderTable(providers));
531
+ const selection = (await askInput(rl, "Select")).trim().toLowerCase();
532
+ if (selection === "q") {
533
+ printStatus("ok", "done");
534
+ return;
535
+ }
536
+ if (selection === "a") {
537
+ await addProvider(rl);
538
+ continue;
539
+ }
540
+ const selectedIndex = Number.parseInt(selection, 10);
541
+ if (Number.isInteger(selectedIndex) &&
542
+ selectedIndex >= 1 &&
543
+ selectedIndex <= providers.length) {
544
+ await showProviderDetail(rl, providers[selectedIndex - 1].provider);
545
+ continue;
546
+ }
547
+ printStatus("warn", `enter 1-${providers.length}, a, or q`);
548
+ }
549
+ }
550
+ finally {
551
+ if (ownReadline) {
552
+ rl.close();
553
+ }
554
+ }
555
+ }
556
+ export const setupKeysTestHooks = {
557
+ chooseKeyDestination,
558
+ copyToKeychain,
559
+ deleteKey,
560
+ listKeyLocations,
561
+ removeDotenvKeys,
562
+ updateKey,
563
+ validateProviderKey,
564
+ };
@@ -0,0 +1,4 @@
1
+ export type SetupRemoteCommandOptions = {
2
+ path?: string;
3
+ };
4
+ export declare function runSetupRemoteCommand(opts: SetupRemoteCommandOptions): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { GnosysDB } from "./db.js";
2
+ export async function runSetupRemoteCommand(opts) {
3
+ let db = null;
4
+ try {
5
+ db = GnosysDB.openLocal();
6
+ if (!db.isAvailable()) {
7
+ console.error("Central DB not available.");
8
+ process.exitCode = 1;
9
+ return;
10
+ }
11
+ if (opts.path) {
12
+ const { configureFromPath } = await import("./remoteWizard.js");
13
+ await configureFromPath(db, opts.path);
14
+ }
15
+ else {
16
+ const { runConfigureWizard } = await import("./remoteWizard.js");
17
+ await runConfigureWizard(db);
18
+ }
19
+ }
20
+ catch (err) {
21
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
22
+ process.exitCode = 1;
23
+ return;
24
+ }
25
+ finally {
26
+ db?.close();
27
+ }
28
+ }
@@ -0,0 +1,5 @@
1
+ export type SetupRemotePullCommandOptions = {
2
+ newerWins?: boolean;
3
+ verbose?: boolean;
4
+ };
5
+ export declare function runSetupRemotePullCommand(opts: SetupRemotePullCommandOptions): Promise<void>;