vigthoria-cli 1.13.18 → 1.13.22

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 (185) 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 +84 -135
  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 +5 -42
  12. package/dist/commands/chat.js +462 -1122
  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 +32 -42
  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 +200 -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 +245 -196
  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 +338 -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 +122 -1498
  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 +45 -269
  71. package/dist/utils/api.js +664 -3210
  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 +227 -99
  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 +6 -7
  106. package/dist/utils/fastAgentRouter.js +21 -30
  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 +13 -0
  128. package/dist/utils/preview-screenshot-adapter.js +36 -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/secret-policy.d.ts +20 -0
  143. package/dist/utils/secret-policy.js +160 -0
  144. package/dist/utils/session.d.ts +34 -2
  145. package/dist/utils/session.js +354 -110
  146. package/dist/utils/tool-approval-service.d.ts +17 -0
  147. package/dist/utils/tool-approval-service.js +78 -0
  148. package/dist/utils/tool-capability-providers.d.ts +57 -0
  149. package/dist/utils/tool-capability-providers.js +57 -0
  150. package/dist/utils/tools.d.ts +2 -23
  151. package/dist/utils/tools.js +216 -508
  152. package/dist/utils/update-policy.d.ts +18 -0
  153. package/dist/utils/update-policy.js +137 -0
  154. package/dist/utils/v3-agent-client.d.ts +40 -0
  155. package/dist/utils/v3-agent-client.js +165 -0
  156. package/dist/utils/v3-workspace-service.d.ts +28 -0
  157. package/dist/utils/v3-workspace-service.js +279 -0
  158. package/dist/utils/vigflow-client.d.ts +95 -0
  159. package/dist/utils/vigflow-client.js +105 -0
  160. package/dist/utils/workspace-boundary.d.ts +45 -0
  161. package/dist/utils/workspace-boundary.js +377 -0
  162. package/dist/utils/workspace-brain-service.d.ts +2 -0
  163. package/dist/utils/workspace-brain-service.js +11 -1
  164. package/dist/utils/workspace-cache.js +37 -15
  165. package/dist/utils/workspace-stream.js +35 -10
  166. package/install.ps1 +86 -143
  167. package/install.sh +76 -81
  168. package/package.json +44 -9
  169. package/release-policy.json +43 -0
  170. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
  171. package/scripts/release/generate-completions.mjs +24 -0
  172. package/scripts/release/install-release.mjs +10 -0
  173. package/scripts/release/publish-cli-release.mjs +116 -0
  174. package/scripts/release/publish-cli-release.sh +2 -70
  175. package/scripts/release/resolve-release-manifest.mjs +26 -0
  176. package/scripts/release/test-balanced-model-live.sh +42 -0
  177. package/scripts/release/validate-live-service-gates.sh +90 -0
  178. package/scripts/release/validate-no-go-gates.sh +135 -172
  179. package/scripts/release/verify-runtime-consistency.mjs +4 -1
  180. package/dist/commands/index.d.ts +0 -12
  181. package/dist/commands/index.js +0 -194
  182. package/dist/commands/update.d.ts +0 -9
  183. package/dist/commands/update.js +0 -301
  184. package/dist/utils/templateInstantPath.d.ts +0 -18
  185. package/dist/utils/templateInstantPath.js +0 -228
@@ -8,9 +8,12 @@
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
10
  import { createHash } from 'node:crypto';
11
+ import { redactSensitiveText } from './secret-policy.js';
12
+ import { DurableJsonStore } from './durable-json.js';
11
13
  const MAX_ITEMS = 240;
12
14
  const MAX_TEXT_LENGTH = 320;
13
15
  const MAX_CONTEXT_ITEMS = 14;
16
+ const MAX_BRAIN_BYTES = 512 * 1024;
14
17
  const STOP_WORDS = new Set([
15
18
  'about', 'after', 'again', 'also', 'because', 'before', 'could', 'from', 'have', 'into', 'just', 'make', 'need',
16
19
  'only', 'please', 'should', 'that', 'their', 'there', 'this', 'with', 'work', 'working', 'would', 'your', 'vigthoria',
@@ -19,7 +22,7 @@ function nowIso() {
19
22
  return new Date().toISOString();
20
23
  }
21
24
  function normalizeText(value) {
22
- return String(value || '')
25
+ return redactSensitiveText(String(value || ''))
23
26
  .replace(/<tool_call>[\s\S]*?<\/tool_call>/g, ' ')
24
27
  .replace(/[ \t\r\n]+/g, ' ')
25
28
  .trim();
@@ -63,43 +66,75 @@ export class ProjectMemoryService {
63
66
  workspacePath;
64
67
  memoryDir;
65
68
  brainPath;
66
- constructor(workspacePath = process.cwd()) {
69
+ brainStore;
70
+ enabled;
71
+ constructor(workspacePath = process.cwd(), options = {}) {
67
72
  this.workspacePath = path.resolve(workspacePath || process.cwd());
68
73
  this.memoryDir = path.join(this.workspacePath, '.vigthoria', 'memory');
69
74
  this.brainPath = path.join(this.memoryDir, 'brain.json');
75
+ const configured = String(process.env.VIGTHORIA_PROJECT_MEMORY || 'local-only').trim().toLowerCase();
76
+ this.enabled = options.enabled ?? !['0', 'off', 'false', 'disabled'].includes(configured);
77
+ this.brainStore = new DurableJsonStore({
78
+ filePath: this.brainPath,
79
+ schemaVersion: 2,
80
+ defaultValue: () => this.emptyBrain(),
81
+ validate: (value) => Boolean(value && typeof value === 'object'
82
+ && value.version === 2
83
+ && value.privacyMode === 'local-only'
84
+ && Array.isArray(value.items)
85
+ && Buffer.byteLength(JSON.stringify(value), 'utf8') <= MAX_BRAIN_BYTES),
86
+ migrate: (value) => {
87
+ if (!value || typeof value !== 'object' || !Array.isArray(value.items))
88
+ return this.emptyBrain();
89
+ const legacy = value;
90
+ return {
91
+ ...legacy,
92
+ version: 2,
93
+ privacyMode: 'local-only',
94
+ workspaceName: legacy.workspaceName || path.basename(this.workspacePath),
95
+ updatedAt: legacy.updatedAt || nowIso(),
96
+ items: this.dedupeAndTrim(legacy.items),
97
+ };
98
+ },
99
+ });
70
100
  }
71
101
  getMemoryDir() {
72
102
  return this.memoryDir;
73
103
  }
74
104
  loadBrain() {
105
+ if (!this.enabled)
106
+ return this.emptyBrain();
75
107
  try {
76
- if (fs.existsSync(this.brainPath)) {
77
- const parsed = JSON.parse(fs.readFileSync(this.brainPath, 'utf8'));
78
- if (parsed && parsed.version === 1 && Array.isArray(parsed.items)) {
79
- return parsed;
80
- }
81
- }
108
+ return this.brainStore.read();
82
109
  }
83
110
  catch {
84
111
  // Corrupt memory should never break chat.
85
112
  }
113
+ return this.emptyBrain();
114
+ }
115
+ emptyBrain() {
86
116
  return {
87
- version: 1,
117
+ version: 2,
88
118
  workspaceName: path.basename(this.workspacePath),
89
119
  updatedAt: nowIso(),
120
+ privacyMode: 'local-only',
90
121
  items: [],
91
122
  };
92
123
  }
93
124
  saveBrain(brain) {
125
+ if (!this.enabled)
126
+ return;
94
127
  try {
95
- fs.mkdirSync(this.memoryDir, { recursive: true });
96
- const normalized = {
128
+ fs.mkdirSync(this.memoryDir, { recursive: true, mode: 0o700 });
129
+ const normalized = this.brainStore.update((current) => ({
130
+ ...current,
97
131
  ...brain,
98
- workspaceName: brain.workspaceName || path.basename(this.workspacePath),
132
+ version: 2,
133
+ privacyMode: 'local-only',
134
+ workspaceName: brain.workspaceName || current.workspaceName || path.basename(this.workspacePath),
99
135
  updatedAt: nowIso(),
100
- items: this.dedupeAndTrim(brain.items || []),
101
- };
102
- fs.writeFileSync(this.brainPath, JSON.stringify(normalized, null, 2) + '\n', 'utf8');
136
+ items: this.dedupeAndTrim([...(current.items || []), ...(brain.items || [])]),
137
+ })).data;
103
138
  this.writeMarkdownViews(normalized);
104
139
  }
105
140
  catch {
@@ -107,6 +142,8 @@ export class ProjectMemoryService {
107
142
  }
108
143
  }
109
144
  remember(type, text, options = {}) {
145
+ if (!this.enabled)
146
+ return;
110
147
  const clean = clampText(text);
111
148
  if (!clean || clean.length < 12)
112
149
  return;
@@ -116,6 +153,8 @@ export class ProjectMemoryService {
116
153
  this.saveBrain(brain);
117
154
  }
118
155
  rememberConversation(messages, options = {}) {
156
+ if (!this.enabled)
157
+ return;
119
158
  const recent = (messages || []).slice(-16);
120
159
  const candidates = [];
121
160
  for (const message of recent) {
@@ -151,6 +190,8 @@ export class ProjectMemoryService {
151
190
  this.saveBrain(brain);
152
191
  }
153
192
  buildContextForPrompt(prompt) {
193
+ if (!this.enabled)
194
+ return '';
154
195
  const items = this.retrieveRelevantMemory(prompt, MAX_CONTEXT_ITEMS);
155
196
  if (items.length === 0)
156
197
  return '';
@@ -205,6 +246,8 @@ export class ProjectMemoryService {
205
246
  itemCount: brain.items.length,
206
247
  updatedAt: brain.updatedAt,
207
248
  typeCounts,
249
+ enabled: this.enabled,
250
+ privacyMode: 'local-only',
208
251
  };
209
252
  }
210
253
  createItem(type, text, options) {
@@ -270,10 +313,20 @@ export class ProjectMemoryService {
270
313
  continue;
271
314
  const heading = this.headingForType(type);
272
315
  const lines = [`# ${heading}`, '', ...items.map((item) => `- ${item.text}`)];
273
- fs.writeFileSync(path.join(this.memoryDir, `${type}s.md`), lines.join('\n') + '\n', 'utf8');
316
+ this.atomicWrite(path.join(this.memoryDir, `${type}s.md`), lines.join('\n') + '\n');
274
317
  allLines.push(`## ${heading}`, '', ...items.slice(-40).map((item) => `- ${item.text}`), '');
275
318
  }
276
- fs.writeFileSync(path.join(this.memoryDir, 'README.md'), allLines.join('\n').trimEnd() + '\n', 'utf8');
319
+ this.atomicWrite(path.join(this.memoryDir, 'README.md'), allLines.join('\n').trimEnd() + '\n');
320
+ }
321
+ atomicWrite(targetPath, content) {
322
+ if (Buffer.byteLength(content, 'utf8') > MAX_BRAIN_BYTES) {
323
+ throw new Error('Project memory write exceeds size limit');
324
+ }
325
+ const temporary = `${targetPath}.tmp-${process.pid}-${Date.now()}`;
326
+ fs.writeFileSync(temporary, content, { encoding: 'utf8', mode: 0o600 });
327
+ fs.renameSync(temporary, targetPath);
328
+ if (process.platform !== 'win32')
329
+ fs.chmodSync(targetPath, 0o600);
277
330
  }
278
331
  headingForType(type) {
279
332
  switch (type) {
@@ -0,0 +1,14 @@
1
+ export interface PromptWorkspaceResolutionOptions {
2
+ baseWorkspace: string;
3
+ currentProjectPath: string;
4
+ cwd: string;
5
+ configuredProjectRoot?: string | null;
6
+ additionalRoots?: string[];
7
+ allowUnscoped?: boolean;
8
+ }
9
+ export interface PromptWorkspaceResolution {
10
+ resolvedPath: string | null;
11
+ rejectedPath?: string;
12
+ allowedRoots: string[];
13
+ }
14
+ export declare function resolvePromptWorkspace(prompt: string, options: PromptWorkspaceResolutionOptions): PromptWorkspaceResolution;
@@ -0,0 +1,64 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ function extractExistingPath(prompt) {
4
+ const windowsMatch = prompt.match(/([A-Za-z]:[\\/][^\s<>"|?*]*)/);
5
+ const candidates = windowsMatch ? [windowsMatch[1]] : [];
6
+ if (!/\b(?:https?|ftp):\/\//i.test(prompt)) {
7
+ const unixMatch = prompt.match(/(\/(?:[^\s<>"|?*]|\s+(?![^\s<>"|?*]))*)/);
8
+ if (unixMatch) {
9
+ candidates.push(unixMatch[1]
10
+ .trim()
11
+ .replace(/\s+(and|or|at|in|the|to|for|with|from|by|on)$/i, '')
12
+ .replace(/[.,;!?:)\]]*$/, '')
13
+ .trim());
14
+ }
15
+ }
16
+ for (const candidate of candidates) {
17
+ if (!candidate || candidate === '/')
18
+ continue;
19
+ try {
20
+ const resolved = path.resolve(candidate);
21
+ if (fs.existsSync(resolved))
22
+ return fs.realpathSync(resolved);
23
+ }
24
+ catch {
25
+ // Continue to the next syntactic candidate.
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ function canonicalDirectory(rawValue) {
31
+ const value = String(rawValue || '').trim();
32
+ if (!value)
33
+ return null;
34
+ try {
35
+ const resolved = path.resolve(value);
36
+ if (!fs.statSync(resolved).isDirectory())
37
+ return null;
38
+ return fs.realpathSync(resolved);
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ function isWithin(candidate, root) {
45
+ const relative = path.relative(root, candidate);
46
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
47
+ }
48
+ export function resolvePromptWorkspace(prompt, options) {
49
+ const explicitPath = extractExistingPath(prompt);
50
+ const allowedRoots = [
51
+ options.baseWorkspace,
52
+ options.currentProjectPath,
53
+ options.cwd,
54
+ options.configuredProjectRoot,
55
+ ...(options.additionalRoots || []),
56
+ ].map(canonicalDirectory).filter((entry) => Boolean(entry));
57
+ const uniqueRoots = [...new Set(allowedRoots)];
58
+ if (!explicitPath)
59
+ return { resolvedPath: null, allowedRoots: uniqueRoots };
60
+ if (options.allowUnscoped || uniqueRoots.some((root) => isWithin(explicitPath, root))) {
61
+ return { resolvedPath: explicitPath, allowedRoots: uniqueRoots };
62
+ }
63
+ return { resolvedPath: null, rejectedPath: explicitPath, allowedRoots: uniqueRoots };
64
+ }
@@ -0,0 +1,11 @@
1
+ export type ReleaseInstallResult = {
2
+ version: string;
3
+ versionDirectory: string;
4
+ currentLink: string;
5
+ previousTarget: string | null;
6
+ binDirectory: string;
7
+ };
8
+ export declare function installReleaseTransaction(packageSpec: string, expectedVersion: string, options?: {
9
+ homeDirectory?: string;
10
+ npmRegistry?: string;
11
+ }): ReleaseInstallResult;
@@ -0,0 +1,155 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { execFileSync } from 'node:child_process';
6
+ import { getReleasePolicy } from './release-policy.js';
7
+ import { safeChildProcessEnv } from './secret-policy.js';
8
+ import { assertProcessNetworkAllowed } from './process-policy.js';
9
+ function npmExecutable() { return process.platform === 'win32' ? 'npm.cmd' : 'npm'; }
10
+ function readInstalledIdentity(prefix) {
11
+ const packageDirectory = path.join(prefix, 'node_modules', 'vigthoria-cli');
12
+ const manifest = JSON.parse(fs.readFileSync(path.join(packageDirectory, 'package.json'), 'utf8'));
13
+ return { name: String(manifest.name || ''), version: String(manifest.version || ''), entry: path.join(packageDirectory, 'dist', 'index.js') };
14
+ }
15
+ function smokeCandidate(prefix, expectedVersion, homeDirectory) {
16
+ const identity = readInstalledIdentity(prefix);
17
+ if (identity.name !== getReleasePolicy().packageName || identity.version !== expectedVersion)
18
+ throw new Error('Staged package identity does not match the selected release.');
19
+ const environment = {
20
+ ...safeChildProcessEnv(),
21
+ HOME: homeDirectory,
22
+ USERPROFILE: homeDirectory,
23
+ VIGTHORIA_OFFLINE: '1',
24
+ VIGTHORIA_NO_BANNER: '1',
25
+ VIGTHORIA_NO_UPDATE_CHECK: '1',
26
+ NO_COLOR: '1',
27
+ };
28
+ for (const args of [['--version'], ['--help'], ['doctor', '--json']]) {
29
+ const output = execFileSync(process.execPath, [identity.entry, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, timeout: 30_000, env: environment });
30
+ if (args[0] === '--version' && output.trim() !== expectedVersion)
31
+ throw new Error('Staged CLI banner does not match the selected release.');
32
+ }
33
+ }
34
+ function writeLaunchers(binDirectory, currentLink) {
35
+ fs.mkdirSync(binDirectory, { recursive: true, mode: 0o700 });
36
+ const entry = path.join(currentLink, 'node_modules', 'vigthoria-cli', 'dist', 'index.js');
37
+ if (process.platform === 'win32') {
38
+ const body = `@echo off\r\n"${process.execPath}" "${entry}" %*\r\n`;
39
+ for (const name of ['vigthoria.cmd', 'vig.cmd', 'vigthoria-chat.cmd']) {
40
+ const temporary = path.join(binDirectory, `.${name}.${randomUUID()}.tmp`);
41
+ fs.writeFileSync(temporary, body, { flag: 'wx' });
42
+ fs.renameSync(temporary, path.join(binDirectory, name));
43
+ }
44
+ return;
45
+ }
46
+ const quotedEntry = entry.replaceAll("'", "'\\''");
47
+ const body = `#!/bin/sh\nexec '${process.execPath}' '${quotedEntry}' "$@"\n`;
48
+ for (const name of ['vigthoria', 'vig', 'vigthoria-chat']) {
49
+ const temporary = path.join(binDirectory, `.${name}.${randomUUID()}.tmp`);
50
+ fs.writeFileSync(temporary, body, { flag: 'wx', mode: 0o755 });
51
+ fs.renameSync(temporary, path.join(binDirectory, name));
52
+ }
53
+ }
54
+ export function installReleaseTransaction(packageSpec, expectedVersion, options = {}) {
55
+ const policy = getReleasePolicy();
56
+ const homeDirectory = path.resolve(options.homeDirectory || os.homedir());
57
+ const cliRoot = path.join(homeDirectory, path.normalize(policy.installation.versionsDirectory), '..');
58
+ const versionsDirectory = path.join(homeDirectory, policy.installation.versionsDirectory);
59
+ const currentLink = path.join(homeDirectory, policy.installation.currentLink);
60
+ const binDirectory = path.join(homeDirectory, policy.installation.binDirectory);
61
+ fs.mkdirSync(versionsDirectory, { recursive: true, mode: 0o700 });
62
+ const stage = path.join(cliRoot, `.stage-${randomUUID()}`);
63
+ const destination = path.join(versionsDirectory, expectedVersion);
64
+ if (fs.existsSync(destination))
65
+ throw new Error(`Version ${expectedVersion} is already installed; refusing to overwrite it.`);
66
+ fs.mkdirSync(stage, { recursive: false, mode: 0o700 });
67
+ let switched = false;
68
+ let previousTarget = null;
69
+ try {
70
+ const npm = npmExecutable();
71
+ const args = ['install', '--prefix', stage, '--ignore-scripts', '--no-audit', '--no-fund', '--install-strategy=nested'];
72
+ const localArchive = path.isAbsolute(packageSpec) && fs.statSync(packageSpec, { throwIfNoEntry: false })?.isFile();
73
+ if (localArchive && process.env.VIGTHORIA_OFFLINE === '1')
74
+ args.push('--offline');
75
+ if (options.npmRegistry)
76
+ args.push(`--registry=${options.npmRegistry}`);
77
+ args.push(packageSpec);
78
+ if (!(localArchive && args.includes('--offline')))
79
+ assertProcessNetworkAllowed(npm, args);
80
+ execFileSync(npm, args, {
81
+ stdio: 'pipe',
82
+ windowsHide: true,
83
+ timeout: 180_000,
84
+ env: {
85
+ ...safeChildProcessEnv(),
86
+ npm_config_cache: path.join(cliRoot, '.npm-cache'),
87
+ npm_config_ignore_scripts: 'true',
88
+ npm_config_audit: 'false',
89
+ npm_config_fund: 'false',
90
+ npm_config_update_notifier: 'false',
91
+ },
92
+ });
93
+ smokeCandidate(stage, expectedVersion, homeDirectory);
94
+ fs.renameSync(stage, destination);
95
+ const currentStat = fs.lstatSync(currentLink, { throwIfNoEntry: false });
96
+ if (currentStat && !currentStat.isSymbolicLink())
97
+ throw new Error('Current release pointer is not a managed symbolic link; refusing to overwrite it.');
98
+ if (currentStat?.isSymbolicLink())
99
+ previousTarget = fs.readlinkSync(currentLink);
100
+ const temporaryLink = `${currentLink}.next-${randomUUID()}`;
101
+ fs.symlinkSync(destination, temporaryLink, process.platform === 'win32' ? 'junction' : 'dir');
102
+ try {
103
+ fs.renameSync(temporaryLink, currentLink);
104
+ }
105
+ catch (error) {
106
+ if (process.platform !== 'win32' || !fs.existsSync(currentLink))
107
+ throw error;
108
+ const backup = `${currentLink}.previous-${randomUUID()}`;
109
+ fs.renameSync(currentLink, backup);
110
+ try {
111
+ fs.renameSync(temporaryLink, currentLink);
112
+ }
113
+ catch (switchError) {
114
+ fs.renameSync(backup, currentLink);
115
+ throw switchError;
116
+ }
117
+ fs.rmSync(backup, { recursive: true, force: true });
118
+ }
119
+ switched = true;
120
+ writeLaunchers(binDirectory, currentLink);
121
+ return { version: expectedVersion, versionDirectory: destination, currentLink, previousTarget, binDirectory };
122
+ }
123
+ catch (error) {
124
+ if (switched) {
125
+ const rollbackLink = `${currentLink}.rollback-${randomUUID()}`;
126
+ try {
127
+ if (previousTarget) {
128
+ fs.symlinkSync(previousTarget, rollbackLink, process.platform === 'win32' ? 'junction' : 'dir');
129
+ fs.renameSync(rollbackLink, currentLink);
130
+ }
131
+ else {
132
+ fs.unlinkSync(currentLink);
133
+ }
134
+ switched = false;
135
+ }
136
+ catch (rollbackError) {
137
+ const detail = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
138
+ throw new Error(`Release activation failed and rollback failed: ${detail}`, { cause: error });
139
+ }
140
+ finally {
141
+ try {
142
+ fs.unlinkSync(rollbackLink);
143
+ }
144
+ catch { }
145
+ }
146
+ }
147
+ if (!switched && fs.existsSync(destination))
148
+ fs.rmSync(destination, { recursive: true, force: true });
149
+ throw error;
150
+ }
151
+ finally {
152
+ if (fs.existsSync(stage))
153
+ fs.rmSync(stage, { recursive: true, force: true });
154
+ }
155
+ }
@@ -0,0 +1,64 @@
1
+ export type ReleasePolicy = {
2
+ schemaVersion: number;
3
+ packageName: string;
4
+ manifest: {
5
+ schemaVersion: number;
6
+ maximumBytes: number;
7
+ requiredChannels: string[];
8
+ allowedChannels: string[];
9
+ };
10
+ origins: {
11
+ manifest: string;
12
+ artifacts: string;
13
+ allowedHosts: string[];
14
+ port: number;
15
+ maximumRedirects: number;
16
+ };
17
+ artifact: {
18
+ maximumBytes: number;
19
+ filenamePattern: string;
20
+ };
21
+ signature: {
22
+ algorithm: 'Ed25519';
23
+ trustedKeys: Record<string, string>;
24
+ };
25
+ channels: Record<string, {
26
+ allowPrerelease: boolean;
27
+ allowDowngrade: boolean;
28
+ }>;
29
+ installation: {
30
+ versionsDirectory: string;
31
+ currentLink: string;
32
+ binDirectory: string;
33
+ preservePrevious: number;
34
+ };
35
+ };
36
+ export type ReleaseEvidence = {
37
+ url: string;
38
+ sha256: string;
39
+ };
40
+ export type ReleaseManifestEntry = {
41
+ version: string;
42
+ url: string;
43
+ sha256: string;
44
+ size: number;
45
+ archiveFilename: string;
46
+ packageName: string;
47
+ packageVersion: string;
48
+ publishedAt: string;
49
+ signature: {
50
+ algorithm: 'Ed25519';
51
+ keyId: string;
52
+ url: string;
53
+ };
54
+ sbom: ReleaseEvidence;
55
+ provenance: ReleaseEvidence;
56
+ notes?: string;
57
+ };
58
+ export declare function getReleasePolicy(): ReleasePolicy;
59
+ export declare function assertReleaseUrl(raw: unknown, kind?: 'manifest' | 'artifact' | 'signature' | 'evidence'): URL;
60
+ export declare function parseReleaseManifest(input: unknown, channel?: string, serializedBytes?: number): ReleaseManifestEntry;
61
+ export declare function parseReleaseManifestText(text: string, channel?: string): ReleaseManifestEntry;
62
+ export declare function releaseSignaturePayload(channel: string, entry: ReleaseManifestEntry): Buffer;
63
+ export declare function verifyReleaseSignature(channel: string, entry: ReleaseManifestEntry, detachedBase64: string, trustedKeys?: Record<string, string>): void;
64
+ export declare function assertReleaseTransition(currentVersion: string, entry: ReleaseManifestEntry, channel: string, allowDowngrade?: boolean): void;