vigthoria-cli 1.13.20 → 1.13.23

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 (187) hide show
  1. package/completions/_vigthoria +49 -0
  2. package/completions/vigthoria.bash +6 -0
  3. package/completions/vigthoria.fish +41 -0
  4. package/dist/commands/auth.d.ts +1 -0
  5. package/dist/commands/auth.js +63 -128
  6. package/dist/commands/background.d.ts +0 -2
  7. package/dist/commands/background.js +26 -34
  8. package/dist/commands/bridge.js +45 -41
  9. package/dist/commands/cancel.d.ts +1 -0
  10. package/dist/commands/cancel.js +22 -16
  11. package/dist/commands/chat.d.ts +3 -42
  12. package/dist/commands/chat.js +378 -1123
  13. package/dist/commands/coding-registration.d.ts +4 -0
  14. package/dist/commands/coding-registration.js +24 -0
  15. package/dist/commands/config.js +23 -29
  16. package/dist/commands/conversation-registration.d.ts +4 -0
  17. package/dist/commands/conversation-registration.js +122 -0
  18. package/dist/commands/deploy.d.ts +8 -2
  19. package/dist/commands/deploy.js +92 -90
  20. package/dist/commands/device.d.ts +1 -2
  21. package/dist/commands/device.js +37 -21
  22. package/dist/commands/doctor-registration.d.ts +4 -0
  23. package/dist/commands/doctor-registration.js +207 -0
  24. package/dist/commands/edit.js +22 -33
  25. package/dist/commands/explain.js +7 -10
  26. package/dist/commands/fork.d.ts +1 -2
  27. package/dist/commands/fork.js +72 -22
  28. package/dist/commands/game.d.ts +1 -1
  29. package/dist/commands/game.js +17 -6
  30. package/dist/commands/generate.js +7 -9
  31. package/dist/commands/history.js +11 -7
  32. package/dist/commands/hub.d.ts +2 -2
  33. package/dist/commands/hub.js +34 -35
  34. package/dist/commands/legion.d.ts +0 -1
  35. package/dist/commands/legion.js +62 -79
  36. package/dist/commands/music.d.ts +1 -2
  37. package/dist/commands/music.js +62 -27
  38. package/dist/commands/platform-registration.d.ts +4 -0
  39. package/dist/commands/platform-registration.js +45 -0
  40. package/dist/commands/preview.d.ts +0 -1
  41. package/dist/commands/preview.js +82 -35
  42. package/dist/commands/product-run-registration.d.ts +4 -0
  43. package/dist/commands/product-run-registration.js +490 -0
  44. package/dist/commands/replay.d.ts +1 -2
  45. package/dist/commands/replay.js +15 -11
  46. package/dist/commands/repo.d.ts +14 -7
  47. package/dist/commands/repo.js +244 -198
  48. package/dist/commands/review.js +9 -12
  49. package/dist/commands/security.d.ts +1 -2
  50. package/dist/commands/security.js +12 -6
  51. package/dist/commands/update-registration.d.ts +3 -0
  52. package/dist/commands/update-registration.js +368 -0
  53. package/dist/commands/v4-registration.d.ts +4 -0
  54. package/dist/commands/v4-registration.js +24 -0
  55. package/dist/commands/v4.d.ts +1 -1
  56. package/dist/commands/v4.js +46 -8
  57. package/dist/commands/wallet.d.ts +1 -3
  58. package/dist/commands/wallet.js +18 -45
  59. package/dist/commands/workflow.d.ts +2 -0
  60. package/dist/commands/workflow.js +52 -39
  61. package/dist/index.js +124 -1589
  62. package/dist/utils/agent-stream-state.d.ts +27 -0
  63. package/dist/utils/agent-stream-state.js +93 -0
  64. package/dist/utils/agentRoute.d.ts +2 -1
  65. package/dist/utils/agentRoute.js +22 -9
  66. package/dist/utils/agentRunOutcome.d.ts +16 -0
  67. package/dist/utils/agentRunOutcome.js +37 -2
  68. package/dist/utils/api-client-factory.d.ts +8 -0
  69. package/dist/utils/api-client-factory.js +8 -0
  70. package/dist/utils/api.d.ts +44 -266
  71. package/dist/utils/api.js +647 -3180
  72. package/dist/utils/auth-session.d.ts +41 -0
  73. package/dist/utils/auth-session.js +224 -0
  74. package/dist/utils/brain-hub-client.d.ts +0 -4
  75. package/dist/utils/brain-hub-client.js +6 -3
  76. package/dist/utils/bridge-client.js +2 -0
  77. package/dist/utils/capability-health-service.d.ts +48 -0
  78. package/dist/utils/capability-health-service.js +64 -0
  79. package/dist/utils/chat-prompt-policy.d.ts +18 -0
  80. package/dist/utils/chat-prompt-policy.js +61 -0
  81. package/dist/utils/chat-tool-call-parser.d.ts +11 -0
  82. package/dist/utils/chat-tool-call-parser.js +71 -0
  83. package/dist/utils/cli-state.d.ts +22 -4
  84. package/dist/utils/cli-state.js +105 -47
  85. package/dist/utils/code-operations-service.d.ts +104 -0
  86. package/dist/utils/code-operations-service.js +837 -0
  87. package/dist/utils/codebase-indexer.d.ts +1 -0
  88. package/dist/utils/codebase-indexer.js +28 -8
  89. package/dist/utils/command-contract.d.ts +61 -0
  90. package/dist/utils/command-contract.js +149 -0
  91. package/dist/utils/command-policy.d.ts +10 -0
  92. package/dist/utils/command-policy.js +54 -0
  93. package/dist/utils/config.d.ts +13 -5
  94. package/dist/utils/config.js +220 -94
  95. package/dist/utils/context-ranker.js +11 -2
  96. package/dist/utils/context-state.d.ts +39 -0
  97. package/dist/utils/context-state.js +52 -0
  98. package/dist/utils/deckEvents.d.ts +0 -5
  99. package/dist/utils/deckEvents.js +3 -2
  100. package/dist/utils/desktop-bridge-client.js +2 -0
  101. package/dist/utils/direct-output-policy.d.ts +3 -0
  102. package/dist/utils/direct-output-policy.js +76 -0
  103. package/dist/utils/durable-json.d.ts +57 -0
  104. package/dist/utils/durable-json.js +319 -0
  105. package/dist/utils/fastAgentRouter.d.ts +5 -6
  106. package/dist/utils/fastAgentRouter.js +17 -25
  107. package/dist/utils/files.js +21 -13
  108. package/dist/utils/frontend-preview-service.d.ts +76 -0
  109. package/dist/utils/frontend-preview-service.js +482 -0
  110. package/dist/utils/goaEvents.d.ts +0 -4
  111. package/dist/utils/goaEvents.js +5 -2
  112. package/dist/utils/logger.d.ts +2 -1
  113. package/dist/utils/logger.js +27 -20
  114. package/dist/utils/mcp-context-client.d.ts +24 -0
  115. package/dist/utils/mcp-context-client.js +58 -0
  116. package/dist/utils/model-governance.d.ts +18 -0
  117. package/dist/utils/model-governance.js +75 -0
  118. package/dist/utils/model-transport-service.d.ts +44 -0
  119. package/dist/utils/model-transport-service.js +111 -0
  120. package/dist/utils/mutation-journal.d.ts +42 -0
  121. package/dist/utils/mutation-journal.js +387 -0
  122. package/dist/utils/network-policy.d.ts +24 -0
  123. package/dist/utils/network-policy.js +202 -0
  124. package/dist/utils/operator-client.d.ts +45 -0
  125. package/dist/utils/operator-client.js +159 -0
  126. package/dist/utils/post-write-validator.js +7 -9
  127. package/dist/utils/preview-screenshot-adapter.d.ts +15 -0
  128. package/dist/utils/preview-screenshot-adapter.js +51 -0
  129. package/dist/utils/process-policy.d.ts +23 -0
  130. package/dist/utils/process-policy.js +284 -0
  131. package/dist/utils/project-memory.d.ts +13 -3
  132. package/dist/utils/project-memory.js +70 -17
  133. package/dist/utils/prompt-workspace-resolver.d.ts +14 -0
  134. package/dist/utils/prompt-workspace-resolver.js +64 -0
  135. package/dist/utils/release-install.d.ts +11 -0
  136. package/dist/utils/release-install.js +155 -0
  137. package/dist/utils/release-policy.d.ts +64 -0
  138. package/dist/utils/release-policy.js +293 -0
  139. package/dist/utils/requestIntent.js +22 -3
  140. package/dist/utils/runtime-capability.d.ts +8 -0
  141. package/dist/utils/runtime-capability.js +16 -0
  142. package/dist/utils/runtime-temp.d.ts +76 -0
  143. package/dist/utils/runtime-temp.js +492 -0
  144. package/dist/utils/secret-policy.d.ts +20 -0
  145. package/dist/utils/secret-policy.js +160 -0
  146. package/dist/utils/session.d.ts +17 -3
  147. package/dist/utils/session.js +354 -110
  148. package/dist/utils/tool-approval-service.d.ts +17 -0
  149. package/dist/utils/tool-approval-service.js +78 -0
  150. package/dist/utils/tool-capability-providers.d.ts +57 -0
  151. package/dist/utils/tool-capability-providers.js +57 -0
  152. package/dist/utils/tools.d.ts +2 -23
  153. package/dist/utils/tools.js +226 -512
  154. package/dist/utils/update-policy.d.ts +18 -0
  155. package/dist/utils/update-policy.js +137 -0
  156. package/dist/utils/v3-agent-client.d.ts +40 -0
  157. package/dist/utils/v3-agent-client.js +165 -0
  158. package/dist/utils/v3-workspace-service.d.ts +28 -0
  159. package/dist/utils/v3-workspace-service.js +279 -0
  160. package/dist/utils/vigflow-client.d.ts +95 -0
  161. package/dist/utils/vigflow-client.js +105 -0
  162. package/dist/utils/workspace-boundary.d.ts +45 -0
  163. package/dist/utils/workspace-boundary.js +377 -0
  164. package/dist/utils/workspace-brain-service.d.ts +2 -0
  165. package/dist/utils/workspace-brain-service.js +11 -1
  166. package/dist/utils/workspace-cache.js +37 -15
  167. package/dist/utils/workspace-stream.js +35 -10
  168. package/install.ps1 +115 -142
  169. package/install.sh +112 -81
  170. package/package.json +43 -9
  171. package/release-policy.json +43 -0
  172. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
  173. package/scripts/release/generate-completions.mjs +24 -0
  174. package/scripts/release/install-release.mjs +10 -0
  175. package/scripts/release/publish-cli-release.mjs +116 -0
  176. package/scripts/release/publish-cli-release.sh +2 -70
  177. package/scripts/release/resolve-release-manifest.mjs +26 -0
  178. package/scripts/release/test-balanced-model-live.sh +45 -0
  179. package/scripts/release/validate-live-service-gates.sh +93 -0
  180. package/scripts/release/validate-no-go-gates.sh +145 -174
  181. package/scripts/release/verify-runtime-consistency.mjs +4 -1
  182. package/dist/commands/index.d.ts +0 -12
  183. package/dist/commands/index.js +0 -194
  184. package/dist/commands/update.d.ts +0 -9
  185. package/dist/commands/update.js +0 -301
  186. package/dist/utils/templateInstantPath.d.ts +0 -18
  187. package/dist/utils/templateInstantPath.js +0 -228
@@ -0,0 +1,492 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ const MIB = 1024 * 1024;
5
+ const DEFAULT_MAX_BYTES = 1024 * MIB;
6
+ const DEFAULT_MIN_FREE_BYTES = 128 * MIB;
7
+ const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
8
+ const CLEANUP_LOCK_MAX_AGE_MS = 10 * 60 * 1000;
9
+ const MAX_SCAN_ENTRIES = 100_000;
10
+ const LEASE_FILE = '.vigthoria-temp-lease.json';
11
+ const CLEANUP_LOCK = '.vigthoria-temp-cleanup.lock';
12
+ const ROOT_MARKER = '.vigthoria-temp-root.json';
13
+ const LEGACY_CLEANUP_MARKER = '.vigthoria-legacy-temp-cleanup-v1';
14
+ const LEGACY_PREFIXES = [
15
+ 'vigthoria-pull-',
16
+ 'vigthoria-pull-inline-',
17
+ 'vigthoria-update-',
18
+ 'vigthoria-cli-install-',
19
+ 'puppeteer_dev_chrome_profile-',
20
+ ];
21
+ export class RuntimeTempError extends Error {
22
+ code;
23
+ details;
24
+ constructor(message, code, details = {}) {
25
+ super(message);
26
+ this.name = 'RuntimeTempError';
27
+ this.code = code;
28
+ this.details = details;
29
+ }
30
+ }
31
+ function numericSetting(name, value, fallback, minimum, maximum) {
32
+ if (!value)
33
+ return fallback;
34
+ const parsed = Number(value);
35
+ if (!Number.isFinite(parsed) || parsed < minimum || parsed > maximum) {
36
+ throw new RuntimeTempError(`${name} must be a number between ${minimum} and ${maximum}.`, 'TEMP_SETTING_INVALID', {
37
+ setting: name,
38
+ });
39
+ }
40
+ return Math.floor(parsed);
41
+ }
42
+ function pathIsContained(root, candidate, pathApi) {
43
+ const relative = pathApi.relative(root, candidate);
44
+ return relative === '' || (!relative.startsWith(`..${pathApi.sep}`) && relative !== '..' && !pathApi.isAbsolute(relative));
45
+ }
46
+ function rejectUnsafeWindowsPath(value) {
47
+ const normalized = value.replace(/\//g, '\\');
48
+ if (/^\\\\[?.]\\/i.test(normalized) || /^\\\\/.test(normalized)) {
49
+ throw new RuntimeTempError('VIGTHORIA_TEMP_DIR cannot use a UNC or Windows device path.', 'TEMP_ROOT_UNSAFE');
50
+ }
51
+ }
52
+ function isKnownSharedPosixTemp(value) {
53
+ const normalized = path.posix.resolve(value);
54
+ return normalized === '/tmp' || normalized === '/var/tmp' || normalized === '/usr/tmp';
55
+ }
56
+ function pathsEqual(left, right, platform) {
57
+ const pathApi = platform === 'win32' ? path.win32 : path.posix;
58
+ const normalize = (value) => {
59
+ const resolved = pathApi.resolve(value);
60
+ return platform === 'win32' ? resolved.toLocaleLowerCase('en-US') : resolved;
61
+ };
62
+ return normalize(left) === normalize(right);
63
+ }
64
+ export function resolveRuntimeTempRoot(options = {}) {
65
+ const environment = options.environment || process.env;
66
+ const platform = options.platform || process.platform;
67
+ const pathApi = platform === 'win32' ? path.win32 : path.posix;
68
+ const homeDirectory = options.homeDirectory || os.homedir();
69
+ const override = String(environment.VIGTHORIA_TEMP_DIR || '').trim();
70
+ if (override.includes('\0'))
71
+ throw new RuntimeTempError('VIGTHORIA_TEMP_DIR contains a null byte.', 'TEMP_ROOT_UNSAFE');
72
+ if (platform === 'win32' && override)
73
+ rejectUnsafeWindowsPath(override);
74
+ if (override && !pathApi.isAbsolute(override)) {
75
+ throw new RuntimeTempError('VIGTHORIA_TEMP_DIR must be an absolute local path.', 'TEMP_ROOT_UNSAFE');
76
+ }
77
+ const root = pathApi.resolve(override || pathApi.join(homeDirectory, '.vigthoria', 'tmp'));
78
+ if (root === pathApi.parse(root).root) {
79
+ throw new RuntimeTempError('The filesystem root cannot be used for Vigthoria temporary storage.', 'TEMP_ROOT_UNSAFE');
80
+ }
81
+ return { root, source: override ? 'explicit-override' : 'per-user-default' };
82
+ }
83
+ function defaultProcessAlive(pid) {
84
+ if (!Number.isSafeInteger(pid) || pid <= 0)
85
+ return false;
86
+ try {
87
+ process.kill(pid, 0);
88
+ return true;
89
+ }
90
+ catch (error) {
91
+ return error?.code === 'EPERM';
92
+ }
93
+ }
94
+ export class RuntimeTempManager {
95
+ environment;
96
+ platform;
97
+ homeDirectory;
98
+ now;
99
+ pid;
100
+ isProcessAlive;
101
+ systemTempRoot;
102
+ configuredRoot;
103
+ source;
104
+ maxBytes;
105
+ minimumFreeBytes;
106
+ ttlMs;
107
+ initializedRoot = null;
108
+ initializedIdentity = null;
109
+ initializationError = null;
110
+ lastCleanup = {
111
+ removedEntries: 0,
112
+ removedBytes: 0,
113
+ skippedActiveEntries: 0,
114
+ legacyRemovedEntries: 0,
115
+ legacyRemovedBytes: 0,
116
+ };
117
+ constructor(options = {}) {
118
+ this.environment = options.environment || process.env;
119
+ this.platform = options.platform || process.platform;
120
+ this.homeDirectory = options.homeDirectory || os.homedir();
121
+ this.now = options.now || Date.now;
122
+ this.pid = options.pid || process.pid;
123
+ this.isProcessAlive = options.isProcessAlive || defaultProcessAlive;
124
+ this.systemTempRoot = options.systemTempDirectory || os.tmpdir();
125
+ const resolved = resolveRuntimeTempRoot({ ...options, environment: this.environment, platform: this.platform, homeDirectory: this.homeDirectory });
126
+ this.configuredRoot = resolved.root;
127
+ this.source = resolved.source;
128
+ this.maxBytes = numericSetting('VIGTHORIA_TEMP_MAX_BYTES', this.environment.VIGTHORIA_TEMP_MAX_BYTES, DEFAULT_MAX_BYTES, 64 * MIB, 64 * 1024 * MIB);
129
+ this.minimumFreeBytes = numericSetting('VIGTHORIA_TEMP_MIN_FREE_BYTES', this.environment.VIGTHORIA_TEMP_MIN_FREE_BYTES, DEFAULT_MIN_FREE_BYTES, 64 * MIB, 64 * 1024 * MIB);
130
+ const ttlHours = numericSetting('VIGTHORIA_TEMP_TTL_HOURS', this.environment.VIGTHORIA_TEMP_TTL_HOURS, DEFAULT_TTL_MS / 3_600_000, 1, 720);
131
+ this.ttlMs = ttlHours * 3_600_000;
132
+ }
133
+ initialize() {
134
+ if (this.initializedRoot || this.initializationError)
135
+ return this.status();
136
+ try {
137
+ if (this.source === 'per-user-default' && !fs.existsSync(this.homeDirectory)) {
138
+ throw new RuntimeTempError('The user home directory does not exist; managed temporary storage cannot be initialized.', 'TEMP_HOME_UNAVAILABLE');
139
+ }
140
+ const existed = fs.existsSync(this.configuredRoot);
141
+ if (existed && fs.lstatSync(this.configuredRoot).isSymbolicLink()) {
142
+ throw new RuntimeTempError('Vigthoria temporary storage root cannot be a symbolic link or junction.', 'TEMP_ROOT_SYMLINK');
143
+ }
144
+ fs.mkdirSync(this.configuredRoot, { recursive: true, mode: 0o700 });
145
+ if (fs.lstatSync(this.configuredRoot).isSymbolicLink()) {
146
+ throw new RuntimeTempError('Vigthoria temporary storage root cannot be a symbolic link or junction.', 'TEMP_ROOT_SYMLINK');
147
+ }
148
+ const realRoot = fs.realpathSync(this.configuredRoot);
149
+ const markerPath = path.join(realRoot, ROOT_MARKER);
150
+ if (this.source === 'explicit-override' && existed && !fs.existsSync(markerPath)) {
151
+ const existingEntries = fs.readdirSync(realRoot);
152
+ if (existingEntries.length > 0) {
153
+ throw new RuntimeTempError('Explicit temporary storage must be empty or already marked as Vigthoria-managed.', 'TEMP_ROOT_NOT_DEDICATED');
154
+ }
155
+ }
156
+ const pathApi = this.platform === 'win32' ? path.win32 : path.posix;
157
+ if (pathsEqual(realRoot, this.systemTempRoot, this.platform)
158
+ || (this.platform !== 'win32' && isKnownSharedPosixTemp(realRoot))) {
159
+ throw new RuntimeTempError('The shared operating-system temporary directory cannot be used for Vigthoria runtime storage.', 'TEMP_ROOT_SHARED');
160
+ }
161
+ if (this.source === 'per-user-default') {
162
+ const realHome = fs.realpathSync(this.homeDirectory);
163
+ if (!pathIsContained(realHome, realRoot, pathApi)) {
164
+ throw new RuntimeTempError('Default temporary storage escaped the user home through a link.', 'TEMP_ROOT_ESCAPE');
165
+ }
166
+ }
167
+ if (this.platform !== 'win32')
168
+ fs.chmodSync(realRoot, 0o700);
169
+ if (!fs.existsSync(markerPath)) {
170
+ try {
171
+ fs.writeFileSync(markerPath, `${JSON.stringify({ schemaVersion: 1, owner: 'vigthoria-cli', createdAt: new Date(this.now()).toISOString() })}\n`, {
172
+ flag: 'wx',
173
+ mode: 0o600,
174
+ });
175
+ }
176
+ catch (error) {
177
+ if (error?.code !== 'EEXIST')
178
+ throw error;
179
+ }
180
+ }
181
+ const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
182
+ if (marker.schemaVersion !== 1 || marker.owner !== 'vigthoria-cli') {
183
+ throw new RuntimeTempError('Temporary storage ownership marker is invalid.', 'TEMP_ROOT_MARKER_INVALID');
184
+ }
185
+ const rootStat = fs.statSync(realRoot, { bigint: true });
186
+ const probe = path.join(realRoot, `.write-probe-${this.pid}-${this.now()}`);
187
+ fs.writeFileSync(probe, '', { flag: 'wx', mode: 0o600 });
188
+ fs.unlinkSync(probe);
189
+ this.initializedRoot = realRoot;
190
+ this.initializedIdentity = { dev: rootStat.dev, ino: rootStat.ino };
191
+ this.environment.TMPDIR = realRoot;
192
+ this.environment.TMP = realRoot;
193
+ this.environment.TEMP = realRoot;
194
+ this.lastCleanup = this.scavenge({ includeLegacy: true });
195
+ }
196
+ catch (error) {
197
+ this.initializationError = error instanceof Error ? error.message : String(error);
198
+ }
199
+ return this.status();
200
+ }
201
+ createDirectory(prefix = 'run-') {
202
+ this.initialize();
203
+ const root = this.requireRoot();
204
+ if (!/^[a-z0-9][a-z0-9-]{0,47}-$/i.test(prefix)) {
205
+ throw new RuntimeTempError('Temporary directory prefix is invalid.', 'TEMP_PREFIX_INVALID');
206
+ }
207
+ this.lastCleanup = this.scavenge({ includeLegacy: false });
208
+ const before = this.measure(root);
209
+ if (before.usedBytes >= this.maxBytes || (before.freeBytes !== null && before.freeBytes < this.minimumFreeBytes)) {
210
+ throw new RuntimeTempError('Vigthoria temporary storage has insufficient capacity after cleanup.', 'TEMP_CAPACITY_EXHAUSTED', {
211
+ root,
212
+ usedBytes: before.usedBytes,
213
+ maxBytes: this.maxBytes,
214
+ freeBytes: before.freeBytes,
215
+ minimumFreeBytes: this.minimumFreeBytes,
216
+ });
217
+ }
218
+ const directory = fs.mkdtempSync(path.join(root, prefix));
219
+ if (this.platform !== 'win32')
220
+ fs.chmodSync(directory, 0o700);
221
+ fs.writeFileSync(path.join(directory, LEASE_FILE), `${JSON.stringify({ schemaVersion: 1, pid: this.pid, createdAt: new Date(this.now()).toISOString() })}\n`, {
222
+ flag: 'wx',
223
+ mode: 0o600,
224
+ });
225
+ return directory;
226
+ }
227
+ removeDirectory(directory) {
228
+ const root = this.requireRoot();
229
+ const absolute = path.resolve(directory);
230
+ if (path.dirname(absolute) !== root || absolute === root) {
231
+ throw new RuntimeTempError('Refusing to remove a path outside the Vigthoria temporary root.', 'TEMP_CLEANUP_ESCAPE');
232
+ }
233
+ fs.rmSync(absolute, { recursive: true, force: true });
234
+ }
235
+ scavenge(options = {}) {
236
+ const root = this.requireRoot();
237
+ const result = {
238
+ removedEntries: 0,
239
+ removedBytes: 0,
240
+ skippedActiveEntries: 0,
241
+ legacyRemovedEntries: 0,
242
+ legacyRemovedBytes: 0,
243
+ };
244
+ const lockPath = path.join(root, CLEANUP_LOCK);
245
+ let lock = null;
246
+ try {
247
+ for (let attempt = 0; attempt < 3 && lock === null; attempt += 1) {
248
+ try {
249
+ lock = fs.openSync(lockPath, 'wx', 0o600);
250
+ }
251
+ catch (error) {
252
+ if (error?.code !== 'EEXIST')
253
+ throw error;
254
+ let lockAge = 0;
255
+ try {
256
+ lockAge = this.now() - fs.statSync(lockPath).mtimeMs;
257
+ }
258
+ catch (statError) {
259
+ if (statError?.code === 'ENOENT')
260
+ continue;
261
+ throw statError;
262
+ }
263
+ if (lockAge <= CLEANUP_LOCK_MAX_AGE_MS)
264
+ return result;
265
+ try {
266
+ fs.unlinkSync(lockPath);
267
+ }
268
+ catch (unlinkError) {
269
+ if (unlinkError?.code !== 'ENOENT')
270
+ throw unlinkError;
271
+ }
272
+ }
273
+ }
274
+ if (lock === null)
275
+ return result;
276
+ fs.writeFileSync(lock, `${this.pid}\n`);
277
+ let entries = this.readEntries(root);
278
+ for (const entry of entries) {
279
+ if (entry.active) {
280
+ result.skippedActiveEntries += 1;
281
+ continue;
282
+ }
283
+ if (this.now() - entry.modifiedAt > this.ttlMs)
284
+ this.removeEntry(entry, result);
285
+ }
286
+ entries = this.readEntries(root);
287
+ let measurement = this.measure(root, entries);
288
+ for (const entry of entries.filter((candidate) => !candidate.active).sort((left, right) => left.modifiedAt - right.modifiedAt)) {
289
+ if (measurement.usedBytes <= this.maxBytes && (measurement.freeBytes === null || measurement.freeBytes >= this.minimumFreeBytes))
290
+ break;
291
+ this.removeEntry(entry, result);
292
+ measurement = this.measure(root);
293
+ }
294
+ if (options.includeLegacy && !fs.existsSync(path.join(root, LEGACY_CLEANUP_MARKER))) {
295
+ this.cleanupLegacySystemTemp(result);
296
+ try {
297
+ fs.writeFileSync(path.join(root, LEGACY_CLEANUP_MARKER), `${new Date(this.now()).toISOString()}\n`, { flag: 'wx', mode: 0o600 });
298
+ }
299
+ catch (error) {
300
+ if (error?.code !== 'EEXIST')
301
+ throw error;
302
+ }
303
+ }
304
+ return result;
305
+ }
306
+ finally {
307
+ if (lock !== null) {
308
+ fs.closeSync(lock);
309
+ try {
310
+ fs.unlinkSync(lockPath);
311
+ }
312
+ catch { /* another process may have recovered a stale lock */ }
313
+ }
314
+ }
315
+ }
316
+ status() {
317
+ const root = this.initializedRoot || this.configuredRoot;
318
+ let measurement = { usedBytes: 0, entryCount: 0, freeBytes: null };
319
+ let runtimeError = this.initializationError;
320
+ if (this.initializedRoot) {
321
+ try {
322
+ measurement = this.measure(this.requireRoot());
323
+ }
324
+ catch (error) {
325
+ runtimeError = error instanceof Error ? error.message : String(error);
326
+ }
327
+ }
328
+ return {
329
+ root,
330
+ source: this.source,
331
+ writable: Boolean(this.initializedRoot) && !runtimeError,
332
+ sharedSystemTemp: this.initializedRoot ? pathsEqual(this.initializedRoot, this.systemTempRoot, this.platform) : false,
333
+ maxBytes: this.maxBytes,
334
+ minimumFreeBytes: this.minimumFreeBytes,
335
+ ttlHours: this.ttlMs / 3_600_000,
336
+ usedBytes: measurement.usedBytes,
337
+ entryCount: measurement.entryCount,
338
+ freeBytes: measurement.freeBytes,
339
+ error: runtimeError,
340
+ ...this.lastCleanup,
341
+ };
342
+ }
343
+ requireRoot() {
344
+ if (!this.initializedRoot) {
345
+ throw new RuntimeTempError(this.initializationError || 'Vigthoria temporary storage is unavailable.', 'TEMP_STORAGE_UNAVAILABLE', {
346
+ root: this.configuredRoot,
347
+ });
348
+ }
349
+ try {
350
+ const stat = fs.lstatSync(this.initializedRoot);
351
+ const identity = fs.statSync(this.initializedRoot, { bigint: true });
352
+ if (stat.isSymbolicLink()
353
+ || fs.realpathSync(this.initializedRoot) !== this.initializedRoot
354
+ || !this.initializedIdentity
355
+ || identity.dev !== this.initializedIdentity.dev
356
+ || identity.ino !== this.initializedIdentity.ino) {
357
+ throw new Error('root identity changed');
358
+ }
359
+ }
360
+ catch {
361
+ throw new RuntimeTempError('Vigthoria temporary storage root changed after initialization.', 'TEMP_ROOT_CHANGED', {
362
+ root: this.initializedRoot,
363
+ });
364
+ }
365
+ return this.initializedRoot;
366
+ }
367
+ readEntries(root) {
368
+ const entries = [];
369
+ for (const name of fs.readdirSync(root)) {
370
+ if (name === CLEANUP_LOCK || name === ROOT_MARKER || name === LEGACY_CLEANUP_MARKER)
371
+ continue;
372
+ const absolutePath = path.join(root, name);
373
+ let stat;
374
+ try {
375
+ stat = fs.lstatSync(absolutePath);
376
+ }
377
+ catch (error) {
378
+ if (error?.code === 'ENOENT')
379
+ continue;
380
+ throw error;
381
+ }
382
+ let active = false;
383
+ if (stat.isDirectory() && !stat.isSymbolicLink()) {
384
+ const leasePath = path.join(absolutePath, LEASE_FILE);
385
+ try {
386
+ const lease = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
387
+ active = typeof lease.pid === 'number' && this.isProcessAlive(lease.pid);
388
+ }
389
+ catch { /* third-party and abandoned directories have no live lease */ }
390
+ }
391
+ entries.push({ absolutePath, name, bytes: this.entrySize(absolutePath), modifiedAt: stat.mtimeMs, active });
392
+ }
393
+ return entries;
394
+ }
395
+ entrySize(entryPath) {
396
+ let bytes = 0;
397
+ let visited = 0;
398
+ const walk = (candidate) => {
399
+ visited += 1;
400
+ if (visited > MAX_SCAN_ENTRIES) {
401
+ bytes = Math.max(bytes, this.maxBytes + 1);
402
+ return;
403
+ }
404
+ const stat = fs.lstatSync(candidate);
405
+ bytes += stat.size;
406
+ if (!stat.isDirectory() || stat.isSymbolicLink())
407
+ return;
408
+ for (const name of fs.readdirSync(candidate))
409
+ walk(path.join(candidate, name));
410
+ };
411
+ try {
412
+ walk(entryPath);
413
+ }
414
+ catch {
415
+ return this.maxBytes + 1;
416
+ }
417
+ return bytes;
418
+ }
419
+ freeBytes(root) {
420
+ try {
421
+ const stat = fs.statfsSync(root);
422
+ return Number(stat.bavail) * Number(stat.bsize);
423
+ }
424
+ catch {
425
+ return null;
426
+ }
427
+ }
428
+ measure(root, entries = this.readEntries(root)) {
429
+ return {
430
+ usedBytes: entries.reduce((total, entry) => total + entry.bytes, 0),
431
+ entryCount: entries.length,
432
+ freeBytes: this.freeBytes(root),
433
+ };
434
+ }
435
+ removeEntry(entry, result) {
436
+ if (path.dirname(entry.absolutePath) !== this.requireRoot()) {
437
+ throw new RuntimeTempError('Temporary cleanup target escaped its root.', 'TEMP_CLEANUP_ESCAPE');
438
+ }
439
+ fs.rmSync(entry.absolutePath, { recursive: true, force: true });
440
+ result.removedEntries += 1;
441
+ result.removedBytes += entry.bytes;
442
+ }
443
+ cleanupLegacySystemTemp(result) {
444
+ const legacyRoot = this.systemTempRoot;
445
+ if (!fs.existsSync(legacyRoot) || path.resolve(legacyRoot) === this.requireRoot())
446
+ return;
447
+ const currentUid = typeof process.getuid === 'function' ? process.getuid() : null;
448
+ if (currentUid === null)
449
+ return;
450
+ let scanned = 0;
451
+ for (const name of fs.readdirSync(legacyRoot)) {
452
+ scanned += 1;
453
+ if (scanned > MAX_SCAN_ENTRIES)
454
+ break;
455
+ if (!LEGACY_PREFIXES.some((prefix) => name.startsWith(prefix)))
456
+ continue;
457
+ const candidate = path.join(legacyRoot, name);
458
+ let stat;
459
+ try {
460
+ stat = fs.lstatSync(candidate);
461
+ }
462
+ catch {
463
+ continue;
464
+ }
465
+ if (currentUid !== null && stat.uid !== currentUid)
466
+ continue;
467
+ if (this.now() - stat.mtimeMs <= this.ttlMs)
468
+ continue;
469
+ const bytes = this.entrySize(candidate);
470
+ fs.rmSync(candidate, { recursive: true, force: true });
471
+ result.legacyRemovedEntries += 1;
472
+ result.legacyRemovedBytes += bytes;
473
+ }
474
+ }
475
+ }
476
+ let defaultManager = null;
477
+ export function getRuntimeTempManager() {
478
+ defaultManager ||= new RuntimeTempManager();
479
+ return defaultManager;
480
+ }
481
+ export function initializeRuntimeTempStorage() {
482
+ return getRuntimeTempManager().initialize();
483
+ }
484
+ export function runtimeTempStatus() {
485
+ return getRuntimeTempManager().initialize();
486
+ }
487
+ export function createRuntimeTempDirectory(prefix) {
488
+ return getRuntimeTempManager().createDirectory(prefix);
489
+ }
490
+ export function removeRuntimeTempDirectory(directory) {
491
+ getRuntimeTempManager().removeDirectory(directory);
492
+ }
@@ -0,0 +1,20 @@
1
+ export interface OutboundScanResult<T> {
2
+ value: T;
3
+ findings: string[];
4
+ }
5
+ export declare class OutboundContextError extends Error {
6
+ readonly findings: string[];
7
+ constructor(findings: string[]);
8
+ }
9
+ export declare function isSensitivePath(pathValue: string): boolean;
10
+ export declare function containsHighConfidenceSecret(input: string): boolean;
11
+ export declare function redactSensitiveText(input: string): string;
12
+ export declare function redactSensitiveValue(value: unknown, seen?: WeakSet<object>): unknown;
13
+ /**
14
+ * Final production-output guard. Commands should still redact at their own
15
+ * boundaries, but this prevents an overlooked remote error or JSON object
16
+ * from writing credentials directly to stdout/stderr.
17
+ */
18
+ export declare function installConsoleRedaction(): void;
19
+ export declare function scanOutboundContext<T>(input: T): OutboundScanResult<T>;
20
+ export declare function safeChildProcessEnv(source?: NodeJS.ProcessEnv, additions?: Record<string, string>): NodeJS.ProcessEnv;
@@ -0,0 +1,160 @@
1
+ const REDACTED = '[REDACTED]';
2
+ const SENSITIVE_SEGMENTS = new Set([
3
+ '.git', '.ssh', '.aws', '.azure', '.kube', '.gnupg', '.vigthoria',
4
+ 'credentials', 'credential', 'secrets', 'secret',
5
+ ]);
6
+ const SENSITIVE_BASENAMES = new Set([
7
+ '.env', '.npmrc', '.pypirc', '.netrc', '.git-credentials',
8
+ 'credentials.json', 'credential.json', 'secrets.json', 'secret.json',
9
+ 'auth.json', 'session.json', 'tokens.json', 'token.json',
10
+ 'id_rsa', 'id_dsa', 'id_ecdsa', 'id_ed25519',
11
+ ]);
12
+ const SENSITIVE_EXTENSIONS = new Set([
13
+ '.pem', '.key', '.p12', '.pfx', '.jks', '.keystore', '.crtkey', '.ovpn',
14
+ ]);
15
+ const SECRET_PATTERNS = [
16
+ /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/i,
17
+ /\bAKIA[0-9A-Z]{16}\b/,
18
+ /\b(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{20,}\b/,
19
+ /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/,
20
+ /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/,
21
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/,
22
+ /\b(?:password|passwd|pwd|client_secret|private_key|api[_-]?key|access[_-]?token|refresh[_-]?token|auth[_-]?token)\s*[:=]\s*["']?[A-Za-z0-9_./+=:-]{8,}/i,
23
+ ];
24
+ const SENSITIVE_KEY = /(?:^|[_-])(?:authorization|cookie|password|passwd|pwd|secret|private[_-]?key|api[_-]?key|access[_-]?token|refresh[_-]?token|auth[_-]?token|session)(?:$|[_-])/i;
25
+ const SENSITIVE_ENV_KEY = /(?:TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|COOKIE|SESSION|PRIVATE_KEY|API_KEY|AUTH|AWS_|AZURE_|GITHUB_|GITLAB_|NPM_|SSH_)/i;
26
+ export class OutboundContextError extends Error {
27
+ findings;
28
+ constructor(findings) {
29
+ super(`Outbound context contains prohibited secret material (${findings.join(', ')})`);
30
+ this.name = 'OutboundContextError';
31
+ this.findings = findings;
32
+ }
33
+ }
34
+ export function isSensitivePath(pathValue) {
35
+ const normalized = String(pathValue || '').replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase();
36
+ if (!normalized)
37
+ return false;
38
+ const segments = normalized.split('/').filter(Boolean);
39
+ const basename = segments.at(-1) || '';
40
+ if (segments.some((segment) => SENSITIVE_SEGMENTS.has(segment)))
41
+ return true;
42
+ if (SENSITIVE_BASENAMES.has(basename))
43
+ return true;
44
+ if (basename.startsWith('.env.') || basename.endsWith('.env'))
45
+ return true;
46
+ if (/^(?:credentials?|secrets?)[._-]/.test(basename))
47
+ return true;
48
+ if (/^(?:tokens?|auth|sessions?)\.(?:json|db|sqlite|sqlite3|yaml|yml)$/i.test(basename))
49
+ return true;
50
+ return SENSITIVE_EXTENSIONS.has(basename.slice(basename.lastIndexOf('.')));
51
+ }
52
+ export function containsHighConfidenceSecret(input) {
53
+ const value = String(input || '').replace(/\[REDACTED\]/g, '');
54
+ return SECRET_PATTERNS.some((pattern) => pattern.test(value));
55
+ }
56
+ export function redactSensitiveText(input) {
57
+ return String(input || '')
58
+ .replace(/-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/gi, REDACTED)
59
+ .replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, `Bearer ${REDACTED}`)
60
+ .replace(/\bAKIA[0-9A-Z]{16}\b/g, REDACTED)
61
+ .replace(/\b(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{20,}\b/g, REDACTED)
62
+ .replace(/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, REDACTED)
63
+ .replace(/\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g, REDACTED)
64
+ .replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, REDACTED)
65
+ .replace(/([?&](?:token|access_token|refresh_token|api_key|apikey|key|secret|signature)=)[^&#\s]+/gi, `$1${REDACTED}`)
66
+ .replace(/\b((?:password|passwd|pwd|token|access_token|refresh_token|api_key|apikey|client_secret|private_key|secret|signature)\s*[:=]\s*)["']?[^,;\s"']+["']?/gi, `$1${REDACTED}`)
67
+ .replace(/((?:authorization|cookie|x-api-key|x-service-key)\s*[:=]\s*)[^,;\s]+/gi, `$1${REDACTED}`)
68
+ .replace(/(vigthoria-auth-token=)[^;\s]+/gi, `$1${REDACTED}`);
69
+ }
70
+ export function redactSensitiveValue(value, seen = new WeakSet()) {
71
+ if (typeof value === 'string')
72
+ return redactSensitiveText(value);
73
+ if (value instanceof Error)
74
+ return `${value.name}: ${redactSensitiveText(value.message)}`;
75
+ if (!value || typeof value !== 'object')
76
+ return value;
77
+ if (seen.has(value))
78
+ return '[Circular]';
79
+ seen.add(value);
80
+ if (Array.isArray(value))
81
+ return value.map((entry) => redactSensitiveValue(entry, seen));
82
+ const output = {};
83
+ for (const [key, entry] of Object.entries(value)) {
84
+ output[key] = SENSITIVE_KEY.test(key) ? REDACTED : redactSensitiveValue(entry, seen);
85
+ }
86
+ return output;
87
+ }
88
+ let consoleRedactionInstalled = false;
89
+ /**
90
+ * Final production-output guard. Commands should still redact at their own
91
+ * boundaries, but this prevents an overlooked remote error or JSON object
92
+ * from writing credentials directly to stdout/stderr.
93
+ */
94
+ export function installConsoleRedaction() {
95
+ if (consoleRedactionInstalled)
96
+ return;
97
+ consoleRedactionInstalled = true;
98
+ for (const method of ['log', 'info', 'warn', 'error', 'debug']) {
99
+ const original = console[method].bind(console);
100
+ console[method] = ((...args) => {
101
+ original(...args.map((value) => redactSensitiveValue(value)));
102
+ });
103
+ }
104
+ }
105
+ export function scanOutboundContext(input) {
106
+ const findings = new Set();
107
+ const seen = new WeakSet();
108
+ const visit = (value, location, depth) => {
109
+ if (depth > 24)
110
+ throw new OutboundContextError([`${location}: depth limit exceeded`]);
111
+ if (typeof value === 'string') {
112
+ if (!/\s/.test(value) && isSensitivePath(value)) {
113
+ findings.add(`${location}: sensitive path value excluded`);
114
+ return REDACTED;
115
+ }
116
+ if (containsHighConfidenceSecret(value))
117
+ findings.add(`${location}: secret value redacted`);
118
+ return redactSensitiveText(value);
119
+ }
120
+ if (!value || typeof value !== 'object')
121
+ return value;
122
+ if (seen.has(value))
123
+ throw new OutboundContextError([`${location}: cyclic value`]);
124
+ seen.add(value);
125
+ if (Array.isArray(value))
126
+ return value.map((entry, index) => visit(entry, `${location}[${index}]`, depth + 1));
127
+ const output = {};
128
+ for (const [key, entry] of Object.entries(value)) {
129
+ const child = `${location}.${key}`;
130
+ if (isSensitivePath(key)) {
131
+ findings.add(`${child}: sensitive path excluded`);
132
+ continue;
133
+ }
134
+ if (SENSITIVE_KEY.test(key)) {
135
+ findings.add(`${child}: credential field redacted`);
136
+ output[key] = REDACTED;
137
+ continue;
138
+ }
139
+ output[key] = visit(entry, child, depth + 1);
140
+ }
141
+ return output;
142
+ };
143
+ const value = visit(input, '$', 0);
144
+ if (containsHighConfidenceSecret(JSON.stringify(value))) {
145
+ throw new OutboundContextError(['residual secret after redaction']);
146
+ }
147
+ return { value, findings: [...findings].sort() };
148
+ }
149
+ export function safeChildProcessEnv(source = process.env, additions = {}) {
150
+ const output = {};
151
+ for (const [key, value] of Object.entries(source)) {
152
+ if (value !== undefined && !SENSITIVE_ENV_KEY.test(key))
153
+ output[key] = value;
154
+ }
155
+ for (const [key, value] of Object.entries(additions)) {
156
+ if (!SENSITIVE_ENV_KEY.test(key))
157
+ output[key] = value;
158
+ }
159
+ return output;
160
+ }