@itpay/cli 2.1.34 → 2.1.41
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.
- package/README.md +5 -12
- package/dist/src/client/backend.js +2 -33
- package/dist/src/client/http.js +0 -2
- package/dist/src/commands/buy.js +4 -7
- package/dist/src/commands/cart.js +15 -15
- package/dist/src/commands/catalog.js +7 -17
- package/dist/src/commands/checkout.js +5 -146
- package/dist/src/commands/checkout_handoff.js +10 -48
- package/dist/src/commands/docs.js +18 -7
- package/dist/src/commands/guidance.js +19 -69
- package/dist/src/commands/install.js +3 -6
- package/dist/src/commands/order.js +1 -14
- package/dist/src/commands/orders.js +9 -4
- package/dist/src/commands/pay.js +1 -1
- package/dist/src/commands/readyz.js +2 -2
- package/dist/src/commands/refund.js +5 -5
- package/dist/src/commands/services.js +167 -1712
- package/dist/src/commands/skill.js +5 -8
- package/dist/src/commands/vault.js +20 -27
- package/dist/src/commands/vault_handoff.js +0 -18
- package/dist/src/main.js +58 -294
- package/dist/src/render/markdown.js +3 -0
- package/dist/src/render/qr.js +1 -1
- package/dist/src/state/cart_session.js +1 -4
- package/dist/src/state/client_context.js +0 -72
- package/dist/src/state/config.js +14 -44
- package/dist/src/state/device_authority.js +41 -141
- package/docs/agent/buyer/cart-checkout.json +5 -6
- package/docs/agent/buyer/catalog-list.json +6 -24
- package/docs/agent/buyer/identity-and-sessions.json +9 -25
- package/docs/agent/buyer/install-and-setup.json +15 -11
- package/docs/agent/buyer/orders-refunds.json +5 -12
- package/docs/agent/buyer/payment-flow.json +3 -4
- package/docs/agent/buyer/purchased-content.json +4 -18
- package/docs/agent/buyer/quickstart.json +16 -24
- package/docs/agent/buyer/render-hosts.json +12 -23
- package/docs/agent/buyer/service-feedback.json +1 -2
- package/docs/cli-reference/agent-types.md +2 -7
- package/docs/cli-reference/commands/buy.md +1 -2
- package/docs/cli-reference/commands/cart/add.md +1 -1
- package/docs/cli-reference/commands/cart/clear.md +1 -1
- package/docs/cli-reference/commands/cart/index.md +1 -1
- package/docs/cli-reference/commands/cart/next.md +1 -1
- package/docs/cli-reference/commands/cart/remove.md +1 -1
- package/docs/cli-reference/commands/cart/show.md +1 -1
- package/docs/cli-reference/commands/catalog/index.md +1 -1
- package/docs/cli-reference/commands/catalog/list.md +2 -2
- package/docs/cli-reference/commands/checkout.md +2 -12
- package/docs/cli-reference/commands/device.md +11 -18
- package/docs/cli-reference/commands/docs/index.md +1 -1
- package/docs/cli-reference/commands/docs/list.md +1 -1
- package/docs/cli-reference/commands/docs/search.md +1 -1
- package/docs/cli-reference/commands/docs/show.md +1 -1
- package/docs/cli-reference/commands/install.md +6 -8
- package/docs/cli-reference/commands/next.md +1 -1
- package/docs/cli-reference/commands/order.md +1 -1
- package/docs/cli-reference/commands/pay.md +1 -1
- package/docs/cli-reference/commands/readyz.md +10 -58
- package/docs/cli-reference/commands/refund/cancel.md +1 -1
- package/docs/cli-reference/commands/refund/create.md +1 -1
- package/docs/cli-reference/commands/refund/get.md +2 -2
- package/docs/cli-reference/commands/refund/index.md +1 -1
- package/docs/cli-reference/commands/refund/list.md +1 -1
- package/docs/cli-reference/commands/refund/watch.md +1 -1
- package/docs/cli-reference/commands/services/action.md +3 -24
- package/docs/cli-reference/commands/services/checkout.md +2 -13
- package/docs/cli-reference/commands/services/events.md +1 -1
- package/docs/cli-reference/commands/services/get.md +2 -4
- package/docs/cli-reference/commands/services/index.md +3 -4
- package/docs/cli-reference/commands/services/invoke.md +4 -6
- package/docs/cli-reference/commands/services/list.md +1 -1
- package/docs/cli-reference/commands/services/next.md +12 -105
- package/docs/cli-reference/commands/services/quote.md +3 -3
- package/docs/cli-reference/commands/services/read-result.md +6 -17
- package/docs/cli-reference/commands/services/start.md +2 -2
- package/docs/cli-reference/commands/vault/access.md +1 -5
- package/docs/cli-reference/conventions.md +6 -4
- package/docs/cli-reference/index.md +1 -8
- package/docs/skill-bundle-rollout/01-mcp-authentication.md +134 -0
- package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +210 -0
- package/docs/skill-bundle-rollout/03-platform-publishing.md +161 -0
- package/docs/skill-bundle-rollout/04-first-wave-platforms.md +69 -0
- package/docs/skill-bundle-rollout/05-sync-operations.md +62 -0
- package/docs/skill-bundle-rollout/README.md +151 -0
- package/package.json +13 -20
- package/skills/itpay/SKILL.md +105 -101
- package/assets/sell-preview/index.html +0 -6
- package/assets/sell-preview/preview.js +0 -9
- package/bin/darwin-amd64/itpay-sell +0 -0
- package/bin/darwin-arm64/itpay-sell +0 -0
- package/bin/linux-amd64/itpay-sell +0 -0
- package/bin/linux-arm64/itpay-sell +0 -0
- package/bin/windows-amd64/itpay-sell.exe +0 -0
- package/dist/src/commands/presentation.js +0 -153
- package/dist/src/commands/rail_catalog.js +0 -261
- package/dist/src/render/browser.js +0 -65
- package/dist/src/sell/commands.js +0 -80
- package/dist/src/sell/contract.js +0 -86
- package/dist/src/sell/local.js +0 -260
- package/dist/src/sell/mcp.js +0 -49
- package/dist/src/sell/preview.js +0 -70
- package/dist/src/sell/sync.js +0 -145
- package/dist/src/state/account_auth.js +0 -332
- package/dist/src/state/task_journal.js +0 -90
- package/docs/agent/buyer/rail-booking.json +0 -162
- package/docs/agent/buyer/rail-fast-checkout.json +0 -48
- package/docs/cli-reference/commands/auth.md +0 -96
- package/docs/cli-reference/commands/sell.md +0 -1364
- package/docs/cli-reference/commands/services/page.md +0 -71
- package/docs/cli-reference/commands/services/run.md +0 -156
- package/docs/sell.md +0 -84
- /package/bin/{itp.mjs → itp} +0 -0
package/dist/src/sell/local.js
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import { newBackendClient, loadConfig } from "../state/config.js";
|
|
2
|
-
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, rmSync, readdirSync } from 'node:fs';
|
|
4
|
-
import { resolve, join, dirname } from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { spawn } from 'node:child_process';
|
|
7
|
-
const hash = (value) => 'sha256:' + createHash('sha256').update(value).digest('hex');
|
|
8
|
-
function read(path) { return JSON.parse(readFileSync(path, 'utf8')); }
|
|
9
|
-
function save(path, value) { mkdirSync(dirname(path), { recursive: true, mode: 0o700 }); const tmp = path + '.' + randomUUID() + '.tmp'; writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', { mode: 0o600 }); renameSync(tmp, path); }
|
|
10
|
-
export function project(directory) { const root = resolve(directory); return { root, state: join(root, '.itpay-sell'), config: read(join(root, 'service.json')), yaml: readFileSync(join(root, 'workflow.yaml'), 'utf8') }; }
|
|
11
|
-
export function localDependencies(state) {
|
|
12
|
-
const platformPath = join(state, 'platform-dependencies.json');
|
|
13
|
-
const dependencies = existsSync(platformPath) ? read(platformPath) : {};
|
|
14
|
-
const sources = join(state, 'sources');
|
|
15
|
-
if (existsSync(sources))
|
|
16
|
-
for (const file of readdirSync(sources)) {
|
|
17
|
-
const source = read(join(sources, file));
|
|
18
|
-
for (const operation of source.operations)
|
|
19
|
-
dependencies[operation.provider_operation_version_id] = {
|
|
20
|
-
provider_operation_version_id: operation.provider_operation_version_id,
|
|
21
|
-
provider_key: source.provider_key, operation_key: operation.operation_key,
|
|
22
|
-
operation_hash: operation.operation_hash, operation: operation.operation,
|
|
23
|
-
request_schema: operation.request_schema, response_schema: operation.response_schema,
|
|
24
|
-
retry_policy: operation.retry_policy ?? {},
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
return dependencies;
|
|
28
|
-
}
|
|
29
|
-
export async function companion(input, onEvent) {
|
|
30
|
-
const target = `${process.platform === 'win32' ? 'windows' : process.platform}-${process.arch === 'x64' ? 'amd64' : process.arch}/itpay-sell${process.platform === 'win32' ? '.exe' : ''}`;
|
|
31
|
-
const packaged = fileURLToPath(new URL(`../../../bin/${target}`, import.meta.url));
|
|
32
|
-
const binary = process.env.ITPAY_SELL_RUNNER ?? (existsSync(packaged) ? packaged : fileURLToPath(new URL(`../../bin/${target}`, import.meta.url)));
|
|
33
|
-
if (!existsSync(binary))
|
|
34
|
-
throw new Error(`Local runner missing for ${process.platform}-${process.arch}; install a CLI package including its native runner`);
|
|
35
|
-
return new Promise((done, fail) => {
|
|
36
|
-
const child = spawn(binary, [], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
37
|
-
let buffer = '', stderr = '';
|
|
38
|
-
const events = [];
|
|
39
|
-
child.on('error', fail);
|
|
40
|
-
child.stderr.on('data', chunk => { stderr += String(chunk).slice(0, 2000); });
|
|
41
|
-
child.stdout.on('data', chunk => {
|
|
42
|
-
buffer += String(chunk);
|
|
43
|
-
let index;
|
|
44
|
-
while ((index = buffer.indexOf('\n')) >= 0) {
|
|
45
|
-
const line = buffer.slice(0, index);
|
|
46
|
-
buffer = buffer.slice(index + 1);
|
|
47
|
-
try {
|
|
48
|
-
const event = JSON.parse(line);
|
|
49
|
-
events.push(event);
|
|
50
|
-
onEvent?.(event);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
child.kill();
|
|
54
|
-
fail(new Error('Invalid local runner response'));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
child.on('close', code => {
|
|
59
|
-
if (code !== 0)
|
|
60
|
-
fail(new Error(events.find(event => event.type === 'error')?.value?.message ?? stderr ?? 'Local runner failed'));
|
|
61
|
-
else
|
|
62
|
-
done(events);
|
|
63
|
-
});
|
|
64
|
-
child.stdin.on('error', () => { });
|
|
65
|
-
child.stdin.end(JSON.stringify(input));
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
function last(events, type) { return [...events].reverse().find(event => event.type === type)?.value; }
|
|
69
|
-
export async function localAction(action, directory, options) {
|
|
70
|
-
const root = resolve(directory), state = join(root, '.itpay-sell');
|
|
71
|
-
if (action === 'init') {
|
|
72
|
-
if (existsSync(join(root, 'service.json')) || existsSync(join(root, 'workflow.yaml')) || existsSync(state))
|
|
73
|
-
throw new Error('Project already exists; existing files were not replaced');
|
|
74
|
-
mkdirSync(root, { recursive: true });
|
|
75
|
-
const config = { format_version: 1, service_id: options.serviceId ?? '', public_name: options.name ?? '', pricing: { billing_mode: 'per_call' }, policy: {}, fixtures: [] };
|
|
76
|
-
save(join(root, 'service.json'), config);
|
|
77
|
-
writeFileSync(join(root, 'workflow.yaml'), '# Generate the workflow from locked API contracts using itpay sell guide.\n', { flag: 'wx' });
|
|
78
|
-
save(join(state, 'versions.json'), { versions: [] });
|
|
79
|
-
return { root, next: 'itpay sell sources add --file <openapi> --provider-key <key>', missing: ['workflow', 'price', 'fixtures'] };
|
|
80
|
-
}
|
|
81
|
-
const p = project(root);
|
|
82
|
-
if (action === 'workflow import') {
|
|
83
|
-
if (!options.file)
|
|
84
|
-
throw new Error('--file is required');
|
|
85
|
-
const yaml = readFileSync(resolve(options.file), 'utf8');
|
|
86
|
-
const validation = last(await companion({ action: 'validate', dependencies: localDependencies(state), document: yaml, pricing: p.config.pricing, policy: p.config.policy }), 'validation');
|
|
87
|
-
const backup = join(state, 'imports', hash(p.yaml).slice(7) + '.yaml');
|
|
88
|
-
mkdirSync(dirname(backup), { recursive: true, mode: 0o700 });
|
|
89
|
-
writeFileSync(backup, p.yaml, { mode: 0o600 });
|
|
90
|
-
writeFileSync(join(root, 'workflow.yaml'), yaml);
|
|
91
|
-
return { imported: true, validation, next: 'Resolve all imported API references and save a new workflow version' };
|
|
92
|
-
}
|
|
93
|
-
if (action === 'config') {
|
|
94
|
-
if (options.file) {
|
|
95
|
-
const value = read(resolve(options.file));
|
|
96
|
-
for (const key of Object.keys(value))
|
|
97
|
-
if (!['service_id', 'public_name', 'pricing', 'policy', 'fixtures', 'merchant_id', 'draft_id', 'expected_revision'].includes(key))
|
|
98
|
-
throw new Error('Unsupported setting: ' + key);
|
|
99
|
-
save(join(root, 'service.json'), { ...p.config, ...value });
|
|
100
|
-
}
|
|
101
|
-
return read(join(root, 'service.json'));
|
|
102
|
-
}
|
|
103
|
-
if (action === 'sources add') {
|
|
104
|
-
if ((!options.file && !options.url) || !options.providerKey)
|
|
105
|
-
throw new Error('--file or --url, and --provider-key are required');
|
|
106
|
-
if (options.file && options.url)
|
|
107
|
-
throw new Error('Choose file or URL, not both');
|
|
108
|
-
const raw = options.file ? readFileSync(resolve(options.file), 'utf8') : last(await companion({ action: 'fetch', source_url: options.url }), 'source').document;
|
|
109
|
-
const id = hash(raw).slice(7);
|
|
110
|
-
const operations = last(await companion({ action: 'compile', document: raw, provider_key: options.providerKey }), 'compiled');
|
|
111
|
-
const path = join(state, 'sources', id + '.json');
|
|
112
|
-
save(path, { provider_key: options.providerKey, source_hash: hash(raw), original: raw, operations });
|
|
113
|
-
return { source_hash: hash(raw), operations, instruction: 'Use these exact contracts when constructing workflow.yaml; bind local credentials by environment reference' };
|
|
114
|
-
}
|
|
115
|
-
if (action === 'credentials bind') {
|
|
116
|
-
if (!options.profile || !options.file)
|
|
117
|
-
throw new Error('--profile and --file are required; file must map credential fields to environment variable names, not secret values');
|
|
118
|
-
const bindings = read(resolve(options.file));
|
|
119
|
-
for (const value of Object.values(bindings))
|
|
120
|
-
if (typeof value !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(value))
|
|
121
|
-
throw new Error('Credential bindings must be environment variable names');
|
|
122
|
-
const path = join(state, 'credentials.json');
|
|
123
|
-
save(path, { ...(existsSync(path) ? read(path) : {}), [options.profile]: bindings });
|
|
124
|
-
return { profile: options.profile, bound_fields: Object.keys(bindings) };
|
|
125
|
-
}
|
|
126
|
-
if (action === 'credentials upload') {
|
|
127
|
-
if (!options.confirm || !options.profile || !options.providerKey || !options.merchantId)
|
|
128
|
-
throw new Error('Merchant, provider, local profile and explicit confirmation are required');
|
|
129
|
-
const bindings = read(join(state, 'credentials.json'))[options.profile];
|
|
130
|
-
if (!bindings)
|
|
131
|
-
throw new Error('Local profile not bound');
|
|
132
|
-
const secrets = {};
|
|
133
|
-
for (const [field, env] of Object.entries(bindings)) {
|
|
134
|
-
const value = process.env[String(env)];
|
|
135
|
-
if (!value)
|
|
136
|
-
throw new Error('Missing credential environment for ' + field);
|
|
137
|
-
secrets[field] = value;
|
|
138
|
-
}
|
|
139
|
-
const result = await newBackendClient(loadConfig()).sellRequest({ method: 'POST', path: '/v1/seller/organizations/' + encodeURIComponent(options.merchantId) + '/provider-credential-profiles', body: { provider_key: options.providerKey, profile_key: options.profile, display_name: options.name ?? options.profile, environment: options.environment ?? 'sandbox', secrets, metadata: options.file ? read(resolve(options.file)) : {} } });
|
|
140
|
-
return result;
|
|
141
|
-
}
|
|
142
|
-
const versionsPath = join(state, 'versions.json');
|
|
143
|
-
const versions = read(versionsPath);
|
|
144
|
-
if (action === 'workflow versions save') {
|
|
145
|
-
const validation = last(await companion({ action: 'validate', dependencies: localDependencies(state), document: p.yaml, pricing: p.config.pricing, policy: p.config.policy }), 'validation');
|
|
146
|
-
const version = { version_id: randomUUID(), name: options.name ?? 'Saved version', hash: hash(p.yaml), config_hash: hash(JSON.stringify(p.config)), created_at: new Date().toISOString() };
|
|
147
|
-
save(join(state, 'versions', version.version_id + '.json'), { ...version, document: p.yaml, config: p.config });
|
|
148
|
-
versions.versions.push(version);
|
|
149
|
-
versions.selected = version.version_id;
|
|
150
|
-
save(versionsPath, versions);
|
|
151
|
-
return { ...version, validation };
|
|
152
|
-
}
|
|
153
|
-
if (action === 'workflow versions list')
|
|
154
|
-
return versions;
|
|
155
|
-
if (action === 'workflow versions use' || action === 'workflow versions delete') {
|
|
156
|
-
const version = versions.versions.find((v) => v.version_id === options.version);
|
|
157
|
-
if (!version)
|
|
158
|
-
throw new Error('Saved version not found');
|
|
159
|
-
if (action.endsWith('use')) {
|
|
160
|
-
const saved = read(join(state, 'versions', version.version_id + '.json'));
|
|
161
|
-
writeFileSync(join(root, 'workflow.yaml'), saved.document);
|
|
162
|
-
save(join(root, 'service.json'), saved.config);
|
|
163
|
-
versions.selected = version.version_id;
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
if (!options.confirm)
|
|
167
|
-
throw new Error('Confirm version deletion with --confirm');
|
|
168
|
-
versions.versions = versions.versions.filter((v) => v.version_id !== version.version_id);
|
|
169
|
-
if (versions.selected === version.version_id)
|
|
170
|
-
delete versions.selected;
|
|
171
|
-
rmSync(join(state, 'versions', version.version_id + '.json'));
|
|
172
|
-
const runs = join(state, 'runs');
|
|
173
|
-
if (existsSync(runs))
|
|
174
|
-
for (const run of readdirSync(runs)) {
|
|
175
|
-
const meta = join(runs, run, 'meta.json');
|
|
176
|
-
if (existsSync(meta) && read(meta).version_id === version.version_id)
|
|
177
|
-
rmSync(join(runs, run), { recursive: true });
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
save(versionsPath, versions);
|
|
181
|
-
return versions;
|
|
182
|
-
}
|
|
183
|
-
if (action === 'workflow validate')
|
|
184
|
-
return last(await companion({ action: 'validate', dependencies: localDependencies(state), document: p.yaml, pricing: p.config.pricing, policy: p.config.policy }), 'validation');
|
|
185
|
-
if (action === 'test get') {
|
|
186
|
-
if (!options.run)
|
|
187
|
-
throw new Error('--run is required');
|
|
188
|
-
return read(join(state, 'runs', safeID(options.run), 'report.json'));
|
|
189
|
-
}
|
|
190
|
-
const selected = versions.versions.find((v) => v.version_id === versions.selected);
|
|
191
|
-
if (!selected || selected.hash !== hash(p.yaml) || selected.config_hash !== hash(JSON.stringify(p.config)))
|
|
192
|
-
throw new Error('Save the current workflow and configuration before confirming or running');
|
|
193
|
-
if (action === 'workflow confirm') {
|
|
194
|
-
if (!options.confirm)
|
|
195
|
-
return { version: selected, config: p.config, workflow: p.yaml, instruction: 'Review this exact workflow and service configuration with the user, then confirm' };
|
|
196
|
-
save(join(state, 'confirmation.json'), { version_id: selected.version_id, hash: selected.hash, config_hash: selected.config_hash, confirmed_at: new Date().toISOString() });
|
|
197
|
-
return { confirmed: selected };
|
|
198
|
-
}
|
|
199
|
-
if (action === 'test run' || action === 'test resume') {
|
|
200
|
-
if (!options.confirm)
|
|
201
|
-
throw new Error('Live Provider calls may cost money or change data. Review dependencies and confirm with --confirm');
|
|
202
|
-
const dependencies = localDependencies(state);
|
|
203
|
-
const credentials = {};
|
|
204
|
-
const credentialPath = join(state, 'credentials.json');
|
|
205
|
-
if (existsSync(credentialPath))
|
|
206
|
-
for (const [profile, fields] of Object.entries(read(credentialPath))) {
|
|
207
|
-
const secrets = {};
|
|
208
|
-
for (const [field, env] of Object.entries(fields)) {
|
|
209
|
-
if (!process.env[String(env)])
|
|
210
|
-
throw new Error(`Missing local environment variable for credential field ${field}`);
|
|
211
|
-
secrets[field] = process.env[String(env)];
|
|
212
|
-
}
|
|
213
|
-
credentials[profile] = { ID: profile, Secrets: secrets, Metadata: {} };
|
|
214
|
-
}
|
|
215
|
-
if (action === 'test resume' && !options.run)
|
|
216
|
-
throw new Error('--run is required for resume');
|
|
217
|
-
const runID = options.run ? safeID(options.run) : randomUUID(), runDirectory = join(state, 'runs', runID);
|
|
218
|
-
const metaPath = join(runDirectory, 'meta.json');
|
|
219
|
-
if (action === 'test resume' && !existsSync(metaPath))
|
|
220
|
-
throw new Error('Run does not exist');
|
|
221
|
-
if (existsSync(metaPath) && read(metaPath).version_id !== selected.version_id)
|
|
222
|
-
throw new Error('Run belongs to a different version');
|
|
223
|
-
save(metaPath, { run_id: runID, version_id: selected.version_id, hash: selected.hash, config_hash: selected.config_hash });
|
|
224
|
-
save(join(state, "last-run.json"), { run_id: runID });
|
|
225
|
-
const events = await companion({ action: 'run', document: p.yaml, pricing: p.config.pricing, policy: p.config.policy, fixtures: p.config.fixtures, dependencies, credentials, run_directory: runDirectory, confirmed: true }, event => {
|
|
226
|
-
if (event.type === 'nodes')
|
|
227
|
-
save(join(runDirectory, 'nodes.json'), event.value);
|
|
228
|
-
});
|
|
229
|
-
const result = last(events, 'result');
|
|
230
|
-
save(join(runDirectory, 'report.json'), result);
|
|
231
|
-
return { run_id: runID, ...result };
|
|
232
|
-
}
|
|
233
|
-
throw new Error('Unsupported local action: ' + action);
|
|
234
|
-
}
|
|
235
|
-
function safeID(value) {
|
|
236
|
-
if (!/^[A-Za-z0-9_-]+$/.test(value))
|
|
237
|
-
throw new Error('Invalid local run ID');
|
|
238
|
-
return value;
|
|
239
|
-
}
|
|
240
|
-
export const LOCAL_ACTIONS = ['init', 'config', 'workflow import', 'sources add', 'credentials bind', 'credentials upload', 'workflow validate', 'workflow confirm', 'workflow versions save', 'workflow versions list', 'workflow versions use', 'workflow versions delete', 'test run', 'test resume', 'test get'];
|
|
241
|
-
export function registerLocal(sell) {
|
|
242
|
-
for (const name of LOCAL_ACTIONS) {
|
|
243
|
-
let parent = sell;
|
|
244
|
-
const parts = name.split(' ');
|
|
245
|
-
const leaf = parts.pop();
|
|
246
|
-
for (const part of parts) {
|
|
247
|
-
parent = parent.commands.find(cmd => cmd.name() === part) ?? parent.command(part);
|
|
248
|
-
}
|
|
249
|
-
parent.command(leaf).description('Local Sell: ' + name).option('--project <directory>', 'Project directory', '.').option('--file <file>').option('--url <https-url>').option('--name <name>').option('--service-id <id>').option('--provider-key <key>').option('--profile <id>').option('--merchant-id <id>').option('--environment <environment>').option('--version-id <id>').option('--run <id>').option('--confirm').option('--json').action(async (options) => {
|
|
250
|
-
try {
|
|
251
|
-
const result = await localAction(name.replace('list-local', 'list'), options.project, { ...options, version: options.versionId });
|
|
252
|
-
process.stdout.write(JSON.stringify({ status: 'ok', result }, null, 2) + '\n');
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
process.exitCode = 1;
|
|
256
|
-
process.stdout.write(JSON.stringify({ status: 'error', message: error instanceof Error ? error.message : 'Local action failed' }) + '\n');
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
package/dist/src/sell/mcp.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { SELL_GUIDE, SELL_OPERATIONS, sellRequest } from './contract.js';
|
|
5
|
-
import { LOCAL_ACTIONS, localAction } from './local.js';
|
|
6
|
-
import { newBackendClient, loadConfig } from '../state/config.js';
|
|
7
|
-
import { syncProject } from './sync.js';
|
|
8
|
-
import { preview } from './preview.js';
|
|
9
|
-
export async function serveSellMCP(directory) {
|
|
10
|
-
const server = new McpServer({ name: 'itpay-sell-local', version: SELL_GUIDE.version }, { instructions: 'Use itpay_seller_guide first. Local tests call Provider APIs and may incur cost; disclose side effects and obtain user approval. Never pass secret values in tool arguments. This server uses its configured project directory.' });
|
|
11
|
-
server.tool('itpay_seller_guide', 'Read the publishing guide and constraints', {}, async () => ({ content: [{ type: 'text', text: JSON.stringify({ ...SELL_GUIDE, operations: SELL_OPERATIONS }) }] }));
|
|
12
|
-
for (const action of LOCAL_ACTIONS) {
|
|
13
|
-
server.tool('itpay_seller_local_' + action.replaceAll(' ', '_').replaceAll('-', '_'), `Local ${action}. Paths are user-provided files; never scan for credentials. Confirm only after explicit user approval.`, { merchant_id: z.string().optional(), environment: z.string().optional(), service_id: z.string().optional(), url: z.string().url().optional(), file: z.string().optional(), name: z.string().optional(), provider_key: z.string().optional(), profile: z.string().optional(), version: z.string().optional(), run: z.string().optional(), confirmed: z.boolean().default(false) }, async (args) => {
|
|
14
|
-
try {
|
|
15
|
-
const result = await localAction(action, directory, { ...args, merchantId: args.merchant_id, providerKey: args.provider_key, serviceId: args.service_id, confirm: args.confirmed });
|
|
16
|
-
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
return { isError: true, content: [{ type: 'text', text: error instanceof Error ? error.message : 'Local action failed' }] };
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
server.tool('itpay_seller_sync', 'Upload or download the configured project after explicit user confirmation. Never pass credential values.', { action: z.enum(['push', 'pull']), merchant_id: z.string(), draft_id: z.string().optional(), bindings_file: z.string().optional(), confirmed: z.boolean().default(false) }, async (args) => {
|
|
24
|
-
try {
|
|
25
|
-
const result = await syncProject(args.action, directory, { merchantId: args.merchant_id, draftId: args.draft_id, bindings: args.bindings_file, confirm: args.confirmed });
|
|
26
|
-
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
return { isError: true, content: [{ type: 'text', text: error instanceof Error ? error.message : 'Sync failed' }] };
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
let display;
|
|
33
|
-
server.tool('itpay_seller_preview', 'Open the existing Builder for this local workflow; user can then confirm through the local workflow tool.', {}, async () => { display ??= await preview(directory); return { content: [{ type: 'text', text: JSON.stringify({ url: display.url }) }] }; });
|
|
34
|
-
for (const operation of SELL_OPERATIONS) {
|
|
35
|
-
server.tool('itpay_seller_platform_' + operation.command.replaceAll(' ', '_').replaceAll('-', '_'), `${operation.description}. Input contract: ${JSON.stringify(operation.fields)}. Optional fields: ${JSON.stringify(operation.optional ?? [])}.`, { merchant_id: z.string().optional(), draft_id: z.string().optional(), library_api_id: z.string().optional(), intake_id: z.string().optional(), version_id: z.string().optional(), run_id: z.string().optional(), submission_id: z.string().optional(), input: z.record(z.unknown()).default({}), confirmed: z.boolean().default(false) }, async (args) => {
|
|
36
|
-
try {
|
|
37
|
-
if (operation.confirmation && !args.confirmed)
|
|
38
|
-
throw new Error('Explicit user confirmation required');
|
|
39
|
-
const result = await newBackendClient(loadConfig()).sellRequest(sellRequest(operation, args, args.input));
|
|
40
|
-
return { content: [{ type: 'text', text: JSON.stringify(result ?? {}) }] };
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
return { isError: true, content: [{ type: 'text', text: error instanceof Error ? error.message : 'Platform action failed' }] };
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
process.stdin.on('end', () => display?.close());
|
|
48
|
-
await server.connect(new StdioServerTransport());
|
|
49
|
-
}
|
package/dist/src/sell/preview.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { createServer } from 'node:http';
|
|
2
|
-
import { randomBytes, createHash } from 'node:crypto';
|
|
3
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { resolve, extname } from 'node:path';
|
|
6
|
-
import { project } from './local.js';
|
|
7
|
-
export async function preview(directory) {
|
|
8
|
-
const packaged = fileURLToPath(new URL('../../../assets/sell-preview/', import.meta.url));
|
|
9
|
-
const assets = existsSync(packaged) ? packaged : fileURLToPath(new URL('../../assets/sell-preview/', import.meta.url));
|
|
10
|
-
if (!existsSync(resolve(assets, 'index.html')))
|
|
11
|
-
throw new Error('CLI preview assets are missing; install the complete CLI package');
|
|
12
|
-
const token = randomBytes(24).toString('hex');
|
|
13
|
-
const server = createServer((req, res) => {
|
|
14
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
15
|
-
res.setHeader('Referrer-Policy', 'no-referrer');
|
|
16
|
-
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
17
|
-
if (req.method !== 'GET' || !req.url?.startsWith('/' + token + '/')) {
|
|
18
|
-
res.writeHead(404).end();
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
const path = req.url.slice(token.length + 2).split('?')[0] ?? '';
|
|
23
|
-
if (path === 'document') {
|
|
24
|
-
const p = project(directory);
|
|
25
|
-
const versions = JSON.parse(readFileSync(resolve(p.state, 'versions.json'), 'utf8'));
|
|
26
|
-
const version = versions.versions.find((v) => v.version_id === versions.selected);
|
|
27
|
-
res.setHeader('Content-Type', 'application/json');
|
|
28
|
-
const actualHash = 'sha256:' + createHash('sha256').update(p.yaml).digest('hex');
|
|
29
|
-
let run;
|
|
30
|
-
const marker = resolve(p.state, 'last-run.json');
|
|
31
|
-
if (existsSync(marker)) {
|
|
32
|
-
const id = JSON.parse(readFileSync(marker, 'utf8')).run_id;
|
|
33
|
-
if (typeof id === 'string' && /^[A-Za-z0-9_-]+$/.test(id) && existsSync(resolve(p.state, 'runs', id, 'meta.json'))) {
|
|
34
|
-
const dir = resolve(p.state, 'runs', id), meta = JSON.parse(readFileSync(resolve(dir, 'meta.json'), 'utf8'));
|
|
35
|
-
const traces = existsSync(resolve(dir, 'nodes.json')) ? JSON.parse(readFileSync(resolve(dir, 'nodes.json'), 'utf8')) : [];
|
|
36
|
-
const report = existsSync(resolve(dir, 'report.json')) ? JSON.parse(readFileSync(resolve(dir, 'report.json'), 'utf8')).report : undefined;
|
|
37
|
-
const configHash = 'sha256:' + createHash('sha256').update(JSON.stringify(p.config)).digest('hex');
|
|
38
|
-
const stale = meta.hash !== actualHash || meta.config_hash !== configHash;
|
|
39
|
-
const fixtures = p.config.fixtures ?? [];
|
|
40
|
-
run = { status: stale ? 'stale' : report?.status ?? 'running', issues: report?.issues ?? [], validation: { fixture_runs: fixtures.map((f) => ({ ...f, nodes: traces.filter((t) => t.record_type === 'node' && t.fixture_id === f.fixture_id) })), api_recognitions: [], side_effect_operations: [] } };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
res.end(JSON.stringify({ document: p.yaml, version: version?.hash === actualHash ? version.name : 'Unsaved', hash: actualHash, pricing: p.config.pricing, run }));
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const file = resolve(assets, path || 'index.html');
|
|
47
|
-
if (!file.startsWith(resolve(assets) + '/')) {
|
|
48
|
-
res.writeHead(404).end();
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const types = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.svg': 'image/svg+xml', '.woff2': 'font/woff2' };
|
|
52
|
-
res.setHeader('Content-Type', types[extname(file)] ?? 'application/octet-stream');
|
|
53
|
-
res.end(readFileSync(file));
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
res.writeHead(404).end();
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
return new Promise((done, fail) => {
|
|
60
|
-
server.on('error', fail);
|
|
61
|
-
server.listen(0, '127.0.0.1', () => {
|
|
62
|
-
const address = server.address();
|
|
63
|
-
if (!address || typeof address === 'string') {
|
|
64
|
-
fail(new Error('Preview address unavailable'));
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
done({ url: `http://127.0.0.1:${address.port}/${token}/`, close: () => server.close() });
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
}
|
package/dist/src/sell/sync.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { HttpError } from "../client/http.js";
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
-
import { createHash } from 'node:crypto';
|
|
5
|
-
import { newBackendClient, loadConfig } from '../state/config.js';
|
|
6
|
-
import { companion, project, localDependencies } from './local.js';
|
|
7
|
-
const encode = encodeURIComponent;
|
|
8
|
-
const hash = (value) => 'sha256:' + createHash('sha256').update(value).digest('hex');
|
|
9
|
-
function read(path) { return JSON.parse(readFileSync(path, 'utf8')); }
|
|
10
|
-
function save(path, value) { writeFileSync(path, JSON.stringify(value, null, 2) + '\n', { mode: 0o600 }); }
|
|
11
|
-
function platformWorkflow(draft) {
|
|
12
|
-
const document = draft.arazzo?.arazzo_document;
|
|
13
|
-
if (typeof document !== 'string' || !document.trim())
|
|
14
|
-
throw new Error('Platform response is missing the workflow document');
|
|
15
|
-
return document;
|
|
16
|
-
}
|
|
17
|
-
export async function syncProject(action, directory, options) {
|
|
18
|
-
const p = project(directory), cloudPath = join(p.state, 'cloud.json');
|
|
19
|
-
let cloud = existsSync(cloudPath) ? read(cloudPath) : {};
|
|
20
|
-
const merchant = options.merchantId ?? cloud.merchant_id ?? p.config.merchant_id;
|
|
21
|
-
if (!merchant)
|
|
22
|
-
throw new Error('--merchant-id is required');
|
|
23
|
-
if (cloud.merchant_id && cloud.merchant_id !== merchant)
|
|
24
|
-
throw new Error('Project is bound to a different merchant');
|
|
25
|
-
const api = newBackendClient(loadConfig()), org = '/v1/seller/organizations/' + encode(merchant);
|
|
26
|
-
const call = async (method, path, body) => await api.sellRequest({ method, path, ...(body !== undefined ? { body } : {}) });
|
|
27
|
-
if (action === 'pull') {
|
|
28
|
-
const draftID = options.draftId ?? cloud.draft_id;
|
|
29
|
-
if (!draftID)
|
|
30
|
-
throw new Error('--draft-id is required');
|
|
31
|
-
if (!options.confirm)
|
|
32
|
-
throw new Error('Pull replaces local workflow/settings; inspect changes and confirm first');
|
|
33
|
-
const bundle = await call('GET', `${org}/service-drafts/${encode(draftID)}/bundle`);
|
|
34
|
-
const backup = join(p.state, 'backups', String(Date.now()));
|
|
35
|
-
mkdirSync(backup, { recursive: true, mode: 0o700 });
|
|
36
|
-
writeFileSync(join(backup, 'workflow.yaml'), p.yaml);
|
|
37
|
-
save(join(backup, 'service.json'), p.config);
|
|
38
|
-
const draft = bundle.draft;
|
|
39
|
-
writeFileSync(join(p.root, 'workflow.yaml'), platformWorkflow(draft));
|
|
40
|
-
save(join(p.root, 'service.json'), { ...p.config, service_id: draft.service_id, public_name: draft.public_name, pricing: draft.pricing, policy: draft.policy, fixtures: bundle.fixtures.fixtures });
|
|
41
|
-
save(join(p.state, 'platform-dependencies.json'), bundle.dependencies);
|
|
42
|
-
cloud = { merchant_id: merchant, draft_id: draftID, revision: draft.semantic_revision, hash: draft.semantic_hash };
|
|
43
|
-
save(cloudPath, cloud);
|
|
44
|
-
return { cloud, backup, next: 'Save a new local version. Platform credentials were not downloaded.' };
|
|
45
|
-
}
|
|
46
|
-
const versions = read(join(p.state, 'versions.json'));
|
|
47
|
-
const version = versions.versions.find((v) => v.version_id === versions.selected);
|
|
48
|
-
const confirmation = existsSync(join(p.state, 'confirmation.json')) ? read(join(p.state, 'confirmation.json')) : {};
|
|
49
|
-
if (!version || version.hash !== hash(p.yaml) || version.config_hash !== hash(JSON.stringify(p.config)) || confirmation.version_id !== version.version_id || confirmation.hash !== version.hash || confirmation.config_hash !== version.config_hash)
|
|
50
|
-
throw new Error('Save and confirm the exact current version before uploading');
|
|
51
|
-
const validation = (await companion({ action: 'validate', dependencies: localDependencies(p.state), document: p.yaml, pricing: p.config.pricing, policy: p.config.policy })).find(event => event.type === 'validation')?.value;
|
|
52
|
-
if (!validation?.result?.valid)
|
|
53
|
-
throw new Error('Workflow does not pass validation');
|
|
54
|
-
if (!options.confirm)
|
|
55
|
-
return { local_version: version, cloud, pricing: p.config.pricing, instruction: 'Review the service package and platform API calls, then confirm upload. Platform verification is a separate explicit action.' };
|
|
56
|
-
if (!cloud.draft_id) {
|
|
57
|
-
const drafts = await call('GET', `${org}/service-drafts`);
|
|
58
|
-
const existing = drafts.drafts?.find((d) => d.service_id === p.config.service_id);
|
|
59
|
-
if (existing)
|
|
60
|
-
throw new Error('Service ID already exists. Pull that draft explicitly before updating it.');
|
|
61
|
-
const draft = await call('POST', `${org}/service-drafts`, { service_id: p.config.service_id, public_name: p.config.public_name });
|
|
62
|
-
cloud = { merchant_id: merchant, draft_id: draft.draft_id, revision: draft.semantic_revision, operation_bindings: {} };
|
|
63
|
-
save(cloudPath, cloud);
|
|
64
|
-
}
|
|
65
|
-
const base = `${org}/service-drafts/${encode(cloud.draft_id)}`;
|
|
66
|
-
let current = await call('GET', base + '/workflow');
|
|
67
|
-
if (current.semantic_revision !== cloud.revision)
|
|
68
|
-
throw new Error('Remote draft changed; pull and review before retrying');
|
|
69
|
-
if (cloud.local_version_id === version.version_id && cloud.hash === current.semantic_hash && cloud.version_id) {
|
|
70
|
-
const saved = await call('GET', base + '/workflow/versions');
|
|
71
|
-
if (saved.versions.some((item) => item.version_id === cloud.version_id)) {
|
|
72
|
-
const fixtureState = await call('GET', base + '/fixtures');
|
|
73
|
-
if (fixtureState.revision !== cloud.fixture_revision)
|
|
74
|
-
throw new Error('Remote test inputs changed; pull and review before retrying');
|
|
75
|
-
return { cloud, already_uploaded: true, next: 'Read the platform Guide and verify the saved version' };
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const sources = join(p.state, 'sources');
|
|
79
|
-
cloud.operation_bindings ??= {};
|
|
80
|
-
if (existsSync(sources))
|
|
81
|
-
for (const file of readdirSync(sources)) {
|
|
82
|
-
const source = read(join(sources, file));
|
|
83
|
-
const imported = await call('POST', `${org}/api-intakes`, { provider_key: source.provider_key, document: source.original, media_type: 'application/yaml' });
|
|
84
|
-
for (const local of source.operations) {
|
|
85
|
-
const remote = imported.operations.find((operation) => operation.operation_key === local.operation_key && operation.method === local.method && operation.path === local.path);
|
|
86
|
-
if (!remote)
|
|
87
|
-
throw new Error('Platform import differs from the local API contract');
|
|
88
|
-
if (remote.operation_hash !== local.operation_hash)
|
|
89
|
-
throw new Error('Platform API contract changed; download and review the imported contract');
|
|
90
|
-
cloud.operation_bindings[local.provider_operation_version_id] = remote.provider_operation_version_id;
|
|
91
|
-
}
|
|
92
|
-
save(cloudPath, cloud);
|
|
93
|
-
}
|
|
94
|
-
const credentials = options.bindings ? read(resolve(options.bindings)) : {};
|
|
95
|
-
const document = validation.document;
|
|
96
|
-
for (const workflow of document.workflows)
|
|
97
|
-
for (const step of workflow.steps) {
|
|
98
|
-
const meta = step['x-itpay-operation'];
|
|
99
|
-
if (meta?.type === 'api_call') {
|
|
100
|
-
meta.providerOperationVersionId = cloud.operation_bindings[meta.providerOperationVersionId] ?? meta.providerOperationVersionId;
|
|
101
|
-
if (credentials[meta.credentialProfileId])
|
|
102
|
-
meta.credentialProfileId = credentials[meta.credentialProfileId];
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// Each durable boundary is recorded. On an ambiguous write, pull explicitly;
|
|
106
|
-
// never retry a guessed revision or overwrite another editor's changes.
|
|
107
|
-
current = await call('PUT', base + '/workflow', { expected_revision: cloud.revision, public_name: p.config.public_name, arazzo: document });
|
|
108
|
-
cloud.revision = current.semantic_revision;
|
|
109
|
-
save(cloudPath, cloud);
|
|
110
|
-
current = await call('PUT', base + '/pricing', { expected_revision: cloud.revision, pricing: p.config.pricing, policy: p.config.policy });
|
|
111
|
-
cloud.revision = current.semantic_revision;
|
|
112
|
-
save(cloudPath, cloud);
|
|
113
|
-
let fixtures;
|
|
114
|
-
try {
|
|
115
|
-
fixtures = await call('GET', base + '/fixtures');
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
if (error instanceof HttpError && error.status === 404)
|
|
119
|
-
fixtures = { revision: 0 };
|
|
120
|
-
else
|
|
121
|
-
throw error;
|
|
122
|
-
}
|
|
123
|
-
const savedFixtures = await call('PUT', base + '/fixtures', { expected_revision: fixtures.revision, fixtures: p.config.fixtures });
|
|
124
|
-
const existingVersions = await call('GET', base + '/workflow/versions');
|
|
125
|
-
const saved = existingVersions.versions.find((item) => item.name === version.name && item.arazzo_document === platformWorkflow(current)) ?? await call('POST', base + '/workflow/versions', { name: version.name, source: 'current', expected_revision: cloud.revision, arazzo_document: platformWorkflow(current) });
|
|
126
|
-
cloud.version_id = saved.version_id;
|
|
127
|
-
cloud.fixture_revision = savedFixtures.revision;
|
|
128
|
-
cloud.local_version_id = version.version_id;
|
|
129
|
-
cloud.hash = current.semantic_hash;
|
|
130
|
-
save(cloudPath, cloud);
|
|
131
|
-
await call('POST', base + '/workflow/validate', {});
|
|
132
|
-
return { cloud, platform_verified: false, next: { command: 'itpay sell verify', input: { workflow_version_id: cloud.version_id, expected_semantic_revision: cloud.revision, expected_fixture_revision: cloud.fixture_revision } }, instruction: 'Review and confirm platform verification before any real API calls. Local test success is not platform evidence.' };
|
|
133
|
-
}
|
|
134
|
-
export function registerSync(sell) {
|
|
135
|
-
for (const action of ['push', 'pull'])
|
|
136
|
-
sell.command(action).option('--project <directory>', 'Project directory', '.').option('--merchant-id <id>').option('--draft-id <id>').option('--bindings <file>', 'Local profile ID to platform credential profile ID map').option('--confirm').option('--json').action(async (options) => {
|
|
137
|
-
try {
|
|
138
|
-
process.stdout.write(JSON.stringify({ status: 'ok', result: await syncProject(action, options.project, options) }, null, 2) + '\n');
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
process.exitCode = 1;
|
|
142
|
-
process.stdout.write(JSON.stringify({ status: 'error', message: error instanceof Error ? error.message : 'Sync failed' }) + '\n');
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
}
|