@open-mercato/cli 0.6.8-develop.6910.1.560e304de2 → 0.6.8-develop.6912.1.36161dc9ac

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 (67) hide show
  1. package/dist/agentic/guides/module-facts.json +110 -110
  2. package/dist/agentic/guides/modules/ai_assistant.md +1 -1
  3. package/dist/agentic/guides/modules/api_docs.md +1 -1
  4. package/dist/agentic/guides/modules/api_keys.md +1 -1
  5. package/dist/agentic/guides/modules/attachments.md +1 -1
  6. package/dist/agentic/guides/modules/audit_logs.md +1 -1
  7. package/dist/agentic/guides/modules/auth.md +1 -1
  8. package/dist/agentic/guides/modules/business_rules.md +1 -1
  9. package/dist/agentic/guides/modules/catalog.md +1 -1
  10. package/dist/agentic/guides/modules/channel_gmail.md +1 -1
  11. package/dist/agentic/guides/modules/channel_imap.md +1 -1
  12. package/dist/agentic/guides/modules/checkout.md +1 -1
  13. package/dist/agentic/guides/modules/communication_channels.md +1 -1
  14. package/dist/agentic/guides/modules/configs.md +1 -1
  15. package/dist/agentic/guides/modules/content.md +1 -1
  16. package/dist/agentic/guides/modules/currencies.md +1 -1
  17. package/dist/agentic/guides/modules/customer_accounts.md +1 -1
  18. package/dist/agentic/guides/modules/customers.md +1 -1
  19. package/dist/agentic/guides/modules/dashboards.md +1 -1
  20. package/dist/agentic/guides/modules/data_sync.md +1 -1
  21. package/dist/agentic/guides/modules/design_system.md +1 -1
  22. package/dist/agentic/guides/modules/dictionaries.md +1 -1
  23. package/dist/agentic/guides/modules/directory.md +1 -1
  24. package/dist/agentic/guides/modules/entities.md +1 -1
  25. package/dist/agentic/guides/modules/events.md +1 -1
  26. package/dist/agentic/guides/modules/feature_toggles.md +1 -1
  27. package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
  28. package/dist/agentic/guides/modules/generators.md +1 -1
  29. package/dist/agentic/guides/modules/inbox_ops.md +1 -1
  30. package/dist/agentic/guides/modules/integrations.md +1 -1
  31. package/dist/agentic/guides/modules/messages.md +1 -1
  32. package/dist/agentic/guides/modules/notifications.md +1 -1
  33. package/dist/agentic/guides/modules/onboarding.md +1 -1
  34. package/dist/agentic/guides/modules/payment_gateways.md +1 -1
  35. package/dist/agentic/guides/modules/perspectives.md +1 -1
  36. package/dist/agentic/guides/modules/planner.md +1 -1
  37. package/dist/agentic/guides/modules/portal.md +1 -1
  38. package/dist/agentic/guides/modules/progress.md +1 -1
  39. package/dist/agentic/guides/modules/query_index.md +1 -1
  40. package/dist/agentic/guides/modules/record_locks.md +1 -1
  41. package/dist/agentic/guides/modules/resources.md +1 -1
  42. package/dist/agentic/guides/modules/sales.md +1 -1
  43. package/dist/agentic/guides/modules/scheduler.md +1 -1
  44. package/dist/agentic/guides/modules/search.md +1 -1
  45. package/dist/agentic/guides/modules/security.md +1 -1
  46. package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
  47. package/dist/agentic/guides/modules/sso.md +1 -1
  48. package/dist/agentic/guides/modules/staff.md +1 -1
  49. package/dist/agentic/guides/modules/storage_s3.md +1 -1
  50. package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
  51. package/dist/agentic/guides/modules/sync_excel.md +1 -1
  52. package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
  53. package/dist/agentic/guides/modules/translations.md +1 -1
  54. package/dist/agentic/guides/modules/webhooks.md +1 -1
  55. package/dist/agentic/guides/modules/wms.md +1 -1
  56. package/dist/agentic/guides/modules/workflows.md +1 -1
  57. package/dist/agentic/guides/upstream/manifest.json +1 -1
  58. package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +2 -2
  59. package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +1 -1
  60. package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +11 -1
  61. package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/export-codex-session.mjs +213 -0
  62. package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +24 -3
  63. package/dist/mercato.js +3 -2
  64. package/dist/mercato.js.map +2 -2
  65. package/jest.config.cjs +1 -1
  66. package/package.json +6 -6
  67. package/src/mercato.ts +2 -1
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'node:child_process'
4
+ import {
5
+ closeSync,
6
+ existsSync,
7
+ lstatSync,
8
+ openSync,
9
+ realpathSync,
10
+ unlinkSync,
11
+ writeFileSync,
12
+ } from 'node:fs'
13
+ import { basename, dirname, resolve } from 'node:path'
14
+ import { pathToFileURL } from 'node:url'
15
+
16
+ const MAX_PROTOCOL_BYTES = 25 * 1024 * 1024
17
+ const MAX_EXPORT_BYTES = 20 * 1024 * 1024
18
+ const THREAD_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
19
+
20
+ function fail(message) {
21
+ throw new Error(message)
22
+ }
23
+
24
+ function parseArguments(argumentsList) {
25
+ const allowed = new Set(['thread-id', 'out'])
26
+ const values = new Map()
27
+ for (let index = 0; index < argumentsList.length; index += 1) {
28
+ const argument = argumentsList[index]
29
+ if (!argument.startsWith('--')) fail('Every argument must use a named --flag.')
30
+ const key = argument.slice(2)
31
+ if (!allowed.has(key)) fail(`Unknown argument: --${key}`)
32
+ if (values.has(key)) fail(`Duplicate argument: --${key}`)
33
+ const value = argumentsList[index + 1]
34
+ if (!value || value.startsWith('--')) fail(`Missing value for --${key}.`)
35
+ values.set(key, value)
36
+ index += 1
37
+ }
38
+ for (const required of allowed) {
39
+ if (!values.has(required)) fail(`Missing required argument: --${required}.`)
40
+ }
41
+ return Object.fromEntries(values)
42
+ }
43
+
44
+ function validateThread(thread, expectedThreadId) {
45
+ if (!thread || typeof thread !== 'object' || Array.isArray(thread)) {
46
+ fail('Codex app-server returned no thread object.')
47
+ }
48
+ const returnedIds = [thread.id, thread.sessionId].filter((value) => typeof value === 'string')
49
+ if (!returnedIds.includes(expectedThreadId)) {
50
+ fail('Codex app-server returned a different thread than requested.')
51
+ }
52
+ if (!Array.isArray(thread.turns) || thread.turns.length === 0) {
53
+ fail('Codex app-server returned a thread without turns.')
54
+ }
55
+ return thread
56
+ }
57
+
58
+ function readThreadFromAppServer(threadId, timeoutMilliseconds = 30_000) {
59
+ return new Promise((resolveThread, rejectThread) => {
60
+ const server = spawn('codex', ['app-server', '--stdio'], {
61
+ stdio: ['pipe', 'pipe', 'ignore'],
62
+ })
63
+ let settled = false
64
+ let initialized = false
65
+ let protocolBytes = 0
66
+ let stdoutBuffer = ''
67
+
68
+ const settle = (error, thread) => {
69
+ if (settled) return
70
+ settled = true
71
+ clearTimeout(timeout)
72
+ server.stdin.end()
73
+ if (server.exitCode === null && !server.killed) server.kill()
74
+ if (error) rejectThread(error)
75
+ else resolveThread(thread)
76
+ }
77
+
78
+ const send = (message) => {
79
+ if (!server.stdin.destroyed) server.stdin.write(`${JSON.stringify(message)}\n`)
80
+ }
81
+
82
+ const handleMessage = (line) => {
83
+ if (!line.trim()) return
84
+ let message
85
+ try {
86
+ message = JSON.parse(line)
87
+ } catch {
88
+ settle(new Error('Codex app-server returned malformed JSON.'))
89
+ return
90
+ }
91
+
92
+ if (message.id === 1) {
93
+ if (message.error || !message.result || initialized) {
94
+ settle(new Error('Codex app-server initialization failed.'))
95
+ return
96
+ }
97
+ initialized = true
98
+ send({ method: 'initialized', params: {} })
99
+ send({ id: 2, method: 'thread/read', params: { threadId, includeTurns: true } })
100
+ return
101
+ }
102
+
103
+ if (message.id === 2) {
104
+ if (message.error) {
105
+ settle(new Error('Codex app-server could not read the requested thread.'))
106
+ return
107
+ }
108
+ try {
109
+ settle(null, validateThread(message.result?.thread, threadId))
110
+ } catch (error) {
111
+ settle(error)
112
+ }
113
+ }
114
+ }
115
+
116
+ const timeout = setTimeout(() => {
117
+ settle(new Error('Timed out while reading the Codex thread.'))
118
+ }, timeoutMilliseconds)
119
+
120
+ server.on('error', () => {
121
+ settle(new Error('Could not start the Codex app-server.'))
122
+ })
123
+ server.stdin.on('error', () => {
124
+ if (!settled) settle(new Error('Codex app-server closed its input before returning the thread.'))
125
+ })
126
+ server.on('close', () => {
127
+ if (!settled) settle(new Error('Codex app-server exited before returning the thread.'))
128
+ })
129
+ server.stdout.setEncoding('utf8')
130
+ server.stdout.on('data', (chunk) => {
131
+ protocolBytes += Buffer.byteLength(chunk)
132
+ if (protocolBytes > MAX_PROTOCOL_BYTES) {
133
+ settle(new Error('Codex app-server response exceeds the allowed size.'))
134
+ return
135
+ }
136
+ stdoutBuffer += chunk
137
+ while (stdoutBuffer.includes('\n')) {
138
+ const newlineIndex = stdoutBuffer.indexOf('\n')
139
+ const line = stdoutBuffer.slice(0, newlineIndex)
140
+ stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1)
141
+ handleMessage(line)
142
+ if (settled) return
143
+ }
144
+ })
145
+
146
+ send({
147
+ id: 1,
148
+ method: 'initialize',
149
+ params: {
150
+ clientInfo: { name: 'om-share-this-session', version: '1.0.0' },
151
+ capabilities: { experimentalApi: true },
152
+ },
153
+ })
154
+ })
155
+ }
156
+
157
+ function writeExport(outputPath, thread) {
158
+ const serialized = `${JSON.stringify(thread, null, 2)}\n`
159
+ if (Buffer.byteLength(serialized) > MAX_EXPORT_BYTES) {
160
+ fail('Codex thread export exceeds the allowed size.')
161
+ }
162
+ const descriptor = openSync(outputPath, 'wx', 0o600)
163
+ let complete = false
164
+ try {
165
+ writeFileSync(descriptor, serialized, 'utf8')
166
+ complete = true
167
+ } finally {
168
+ closeSync(descriptor)
169
+ if (!complete && existsSync(outputPath)) unlinkSync(outputPath)
170
+ }
171
+ }
172
+
173
+ export async function exportCodexSession({ threadId, outputPath }) {
174
+ if (!THREAD_ID_PATTERN.test(threadId)) fail('Codex thread ID must be a UUID.')
175
+ const resolvedOutputPath = resolve(outputPath)
176
+ if (basename(resolvedOutputPath) === '.' || existsSync(resolvedOutputPath)) {
177
+ fail('Output path already exists; use a new temporary file.')
178
+ }
179
+ const outputParent = dirname(resolvedOutputPath)
180
+ let parentStat
181
+ try {
182
+ parentStat = lstatSync(outputParent)
183
+ realpathSync(outputParent)
184
+ } catch {
185
+ fail('Output parent must be an existing real directory.')
186
+ }
187
+ if (!parentStat.isDirectory() || parentStat.isSymbolicLink()) {
188
+ fail('Output parent must be an existing real directory.')
189
+ }
190
+
191
+ const thread = await readThreadFromAppServer(threadId)
192
+ writeExport(resolvedOutputPath, thread)
193
+ return { turns: thread.turns.length }
194
+ }
195
+
196
+ async function main() {
197
+ const argumentsMap = parseArguments(process.argv.slice(2))
198
+ const result = await exportCodexSession({
199
+ threadId: argumentsMap['thread-id'],
200
+ outputPath: argumentsMap.out,
201
+ })
202
+ process.stdout.write(`${JSON.stringify({ status: 'exported', turns: result.turns })}\n`)
203
+ }
204
+
205
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
206
+ try {
207
+ await main()
208
+ } catch (error) {
209
+ const message = error instanceof Error ? error.message : 'Unknown Codex session-export failure.'
210
+ process.stderr.write(`Codex session was not exported: ${message}\n`)
211
+ process.exitCode = 1
212
+ }
213
+ }
@@ -161,14 +161,17 @@ function sanitizeString(input, key, state) {
161
161
  value = replaceMatches(value, /\b(Authorization\s*:\s*)[^\s,;]+/gi, (_match, prefix) => `${prefix}«redacted:authorization»`, state, 'secrets')
162
162
  value = replaceMatches(value, /([a-z][a-z0-9+.-]*:\/\/)[^/\s:@]+:[^/\s@]+@/gi, (_match, prefix) => `${prefix}«redacted:credential»@`, state, 'secrets')
163
163
  value = replaceMatches(value, /\b((?:api[_-]?key|access[_-]?token|refresh[_-]?token|password|passwd|secret|client[_-]?secret)\s*[:=]\s*)[^\s,;"']+/gi, (_match, prefix) => `${prefix}«redacted:secret»`, state, 'secrets')
164
- value = redactHighEntropy(value, state)
164
+ if (key !== 'generated-file-path') value = redactHighEntropy(value, state)
165
165
 
166
166
  value = replaceMatches(value, /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, '«redacted:email»', state, 'pii')
167
167
  value = replaceMatches(value, /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, '«redacted:ip»', state, 'pii')
168
168
  value = replaceMatches(value, /\b(?:[A-F0-9]{1,4}:){2,7}[A-F0-9]{1,4}\b/gi, '«redacted:ip»', state, 'pii')
169
169
  value = replaceMatches(value, /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi, '«redacted:identifier»', state, 'identifiers')
170
170
  value = replaceMatches(value, /\b[0-9a-f]{64,}\b/gi, '«redacted:hex-secret»', state, 'secrets')
171
- value = value.replace(/\+?\d[\d ()-]{7,}\d/g, (candidate) => {
171
+ const phonePattern = key === 'generated-file-path'
172
+ ? /(?<![A-Za-z0-9_])\+?\d[\d ()-]{7,}\d(?![A-Za-z0-9_])/g
173
+ : /\+?\d[\d ()-]{7,}\d/g
174
+ value = value.replace(phonePattern, (candidate) => {
172
175
  const digits = candidate.replace(/\D/g, '')
173
176
  if (digits.length < 9 || digits.length > 15) return candidate
174
177
  countReplacement(state, 'pii')
@@ -183,6 +186,10 @@ function sanitizeNode(value, key, state) {
183
186
  if (Array.isArray(value)) return value.map((item) => sanitizeNode(item, '', state))
184
187
  if (!value || typeof value !== 'object') return value
185
188
 
189
+ const redactsBrowserTabListing = value.type === 'mcpToolCall'
190
+ && typeof value.arguments?.code === 'string'
191
+ && /\.user\.openTabs\s*\(/.test(value.arguments.code)
192
+
186
193
  for (const [candidateKey, candidateValue] of Object.entries(value)) {
187
194
  if (pathKeyPattern.test(candidateKey) && typeof candidateValue === 'string' && isDangerousPath(candidateValue)) {
188
195
  countReplacement(state, 'dangerous')
@@ -194,6 +201,11 @@ function sanitizeNode(value, key, state) {
194
201
  for (const [childKey, childValue] of Object.entries(value)) {
195
202
  const sanitizedKey = sanitizeString(childKey, 'object-key', state)
196
203
  if (Object.hasOwn(output, sanitizedKey)) fail('Sanitization produced duplicate object keys.')
204
+ if (redactsBrowserTabListing && childKey === 'result') {
205
+ countReplacement(state, 'pii')
206
+ output[sanitizedKey] = { redacted: 'browser-tab-list' }
207
+ continue
208
+ }
197
209
  output[sanitizedKey] = sanitizeNode(childValue, childKey, state)
198
210
  }
199
211
  return output
@@ -203,10 +215,19 @@ function inferRole(value) {
203
215
  if (!value || typeof value !== 'object') return null
204
216
  for (const candidate of [value.type, value.role, value.message?.role]) {
205
217
  if (candidate === 'user' || candidate === 'assistant') return candidate
218
+ if (candidate === 'userMessage') return 'user'
219
+ if (candidate === 'agentMessage') return 'assistant'
206
220
  }
207
221
  return null
208
222
  }
209
223
 
224
+ function inferRoles(value) {
225
+ const directRole = inferRole(value)
226
+ if (directRole) return [directRole]
227
+ if (!value || typeof value !== 'object' || !Array.isArray(value.items)) return []
228
+ return [...new Set(value.items.map(inferRole).filter(Boolean))]
229
+ }
230
+
210
231
  function analyzeSession(session) {
211
232
  let entries
212
233
  let collectionName
@@ -224,7 +245,7 @@ function analyzeSession(session) {
224
245
  }
225
246
  if (!entries) fail('Session JSON has no recognizable turn/event collection.')
226
247
 
227
- const roles = entries.map(inferRole).filter(Boolean)
248
+ const roles = entries.flatMap(inferRoles)
228
249
  const userTurns = roles.filter((role) => role === 'user').length
229
250
  const assistantTurns = roles.filter((role) => role === 'assistant').length
230
251
  if (userTurns === 0 || assistantTurns === 0) {
package/dist/mercato.js CHANGED
@@ -2,7 +2,7 @@ import { registerWorkerShutdownHook, runWorker } from "@open-mercato/queue/worke
2
2
  import { getCliModules, hasCliModules, registerCliModules } from "./registry.js";
3
3
  import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
4
4
  import { getSslConfig } from "@open-mercato/shared/lib/db/ssl";
5
- import { getRedisUrl, getRedisUrlOrThrow } from "@open-mercato/shared/lib/redis/connection";
5
+ import { getRedisUrl, getRedisUrlOrThrow, REDIS_WIRE_PROTOCOL } from "@open-mercato/shared/lib/redis/connection";
6
6
  import { resolveInitDerivedSecrets } from "./lib/init-secrets.js";
7
7
  import {
8
8
  resolveAutoSpawnWorkersMode,
@@ -764,7 +764,8 @@ async function run(argv = process.argv) {
764
764
  connectTimeout: 3e3,
765
765
  maxRetriesPerRequest: 1,
766
766
  retryStrategy: () => null,
767
- enableOfflineQueue: false
767
+ enableOfflineQueue: false,
768
+ protocol: REDIS_WIRE_PROTOCOL
768
769
  });
769
770
  redis.on("error", () => {
770
771
  });