@storewright/cli 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/VERSION +1 -0
- package/bin/storewright.mjs +62 -0
- package/contracts/action-registry.json +175 -0
- package/contracts/capability-registry.json +63 -0
- package/contracts/workflow-manifest.json +207 -0
- package/lib/cli/storewright-cli.mjs +259 -0
- package/lib/internal/launch-envelope.mjs +223 -0
- package/lib/internal/multi-agent-contracts.mjs +137 -0
- package/lib/internal/operation-ledger.mjs +190 -0
- package/lib/internal/pricing/default-preview-pricing.mjs +181 -0
- package/lib/internal/run-state-helpers.mjs +313 -0
- package/lib/internal/shopify-operation-adapter.mjs +456 -0
- package/package.json +38 -0
- package/schemas/action-registry.schema.json +11 -0
- package/schemas/agent-report.schema.json +14 -0
- package/schemas/approval-grant.schema.json +16 -0
- package/schemas/base-theme-report.schema.json +25 -0
- package/schemas/brand-identity.schema.json +142 -0
- package/schemas/capability-registry.schema.json +11 -0
- package/schemas/competitor-audit.schema.json +38 -0
- package/schemas/design-direction.schema.json +64 -0
- package/schemas/external-operation.schema.json +34 -0
- package/schemas/intake-blocked-report.schema.json +76 -0
- package/schemas/launch-envelope.schema.json +25 -0
- package/schemas/launch-readiness.schema.json +73 -0
- package/schemas/media-file-inspection-report.schema.json +223 -0
- package/schemas/media-manifest.schema.json +84 -0
- package/schemas/merchandising-brief.schema.json +27 -0
- package/schemas/normalized-product-catalog.schema.json +42 -0
- package/schemas/product-content-generation-input.schema.json +40 -0
- package/schemas/product-content-generation-output.schema.json +43 -0
- package/schemas/raw-product-candidates.schema.json +32 -0
- package/schemas/shopify-access-preflight-report.schema.json +213 -0
- package/schemas/shopify-content-sync-report.schema.json +190 -0
- package/schemas/shopify-media-map.schema.json +87 -0
- package/schemas/shopify-media-upload-report.schema.json +96 -0
- package/schemas/shopify-operation-request.schema.json +81 -0
- package/schemas/shopify-preflight-report.schema.json +187 -0
- package/schemas/store-blueprint.schema.json +112 -0
- package/schemas/store-content-generation-output.schema.json +102 -0
- package/schemas/store-intake.schema.json +205 -0
- package/schemas/store-ops-plan.schema.json +82 -0
- package/schemas/storefront-preview-review.schema.json +227 -0
- package/schemas/supplier-access-report.schema.json +36 -0
- package/schemas/supplier-extraction-report.schema.json +185 -0
- package/schemas/theme-build-report.schema.json +43 -0
- package/schemas/theme-code-change-summary.schema.json +65 -0
- package/schemas/theme-plan.schema.json +26 -0
- package/schemas/theme-push-report.schema.json +151 -0
- package/schemas/theme-workspace-validation-report.schema.json +61 -0
- package/schemas/workflow-manifest.schema.json +29 -0
- package/scripts/audit-run-state.mjs +472 -0
- package/scripts/execute-shopify-operation.mjs +190 -0
- package/scripts/generate-image-assets-openai.mjs +342 -0
- package/scripts/generate-media-assets.mjs +121 -0
- package/scripts/init-run-state.mjs +69 -0
- package/scripts/inspect-media-files.mjs +334 -0
- package/scripts/prepare-launch-envelope.mjs +47 -0
- package/scripts/shopify-access-preflight.mjs +432 -0
- package/scripts/upload-shopify-media.mjs +831 -0
- package/scripts/validate-agent-report.mjs +46 -0
- package/scripts/validate-artifact.mjs +196 -0
- package/scripts/validate-launch-envelope.mjs +50 -0
- package/scripts/validate-registries.mjs +50 -0
- package/scripts/validate-workflow-manifest.mjs +38 -0
- package/scripts/version.mjs +192 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { validateAgentReportAgainstEnvelope } from "../lib/internal/launch-envelope.mjs";
|
|
6
|
+
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
const options = {};
|
|
9
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
10
|
+
const arg = argv[index];
|
|
11
|
+
const value = argv[index + 1];
|
|
12
|
+
if (arg === "--envelope") {
|
|
13
|
+
options.envelopePath = value;
|
|
14
|
+
index += 1;
|
|
15
|
+
} else if (arg === "--report") {
|
|
16
|
+
options.reportPath = value;
|
|
17
|
+
index += 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return options;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function validateAgentReportFile({ envelopePath, reportPath }) {
|
|
24
|
+
if (!envelopePath || !reportPath) {
|
|
25
|
+
return { ok: false, errors: ["Missing --envelope or --report argument"] };
|
|
26
|
+
}
|
|
27
|
+
const envelope = JSON.parse(await readFile(envelopePath, "utf8"));
|
|
28
|
+
const report = JSON.parse(await readFile(reportPath, "utf8"));
|
|
29
|
+
return validateAgentReportAgainstEnvelope({ envelope, report });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function main() {
|
|
33
|
+
const result = await validateAgentReportFile(parseArgs(process.argv.slice(2)));
|
|
34
|
+
if (!result.ok) {
|
|
35
|
+
for (const error of result.errors) {
|
|
36
|
+
console.error(error);
|
|
37
|
+
}
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log("Agent report matches launch envelope.");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
45
|
+
await main();
|
|
46
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const packageRoot = dirname(scriptDir);
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const args = {};
|
|
12
|
+
|
|
13
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
14
|
+
const arg = argv[index];
|
|
15
|
+
if (arg === "--schema") {
|
|
16
|
+
args.schemaPath = argv[index + 1];
|
|
17
|
+
index += 1;
|
|
18
|
+
} else if (arg === "--artifact") {
|
|
19
|
+
args.artifactPath = argv[index + 1];
|
|
20
|
+
index += 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return args;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveReadablePath(filePath) {
|
|
28
|
+
if (!filePath || existsSync(filePath)) {
|
|
29
|
+
return filePath;
|
|
30
|
+
}
|
|
31
|
+
const packageRelativePrefix = "storewright/";
|
|
32
|
+
if (filePath.startsWith(packageRelativePrefix)) {
|
|
33
|
+
const packageRelativePath = filePath.slice(packageRelativePrefix.length);
|
|
34
|
+
const resolved = join(packageRoot, packageRelativePath);
|
|
35
|
+
if (existsSync(resolved)) {
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return filePath;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function readJson(filePath, label) {
|
|
43
|
+
if (!filePath) {
|
|
44
|
+
return { ok: false, errors: [`Missing --${label} argument`] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const readablePath = resolveReadablePath(filePath);
|
|
48
|
+
if (!existsSync(readablePath)) {
|
|
49
|
+
return { ok: false, errors: [`${capitalize(label)} file does not exist: ${filePath}`] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
return { ok: true, value: JSON.parse(await readFile(readablePath, "utf8")) };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return { ok: false, errors: [`${capitalize(label)} file is not valid JSON: ${error.message}`] };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function capitalize(value) {
|
|
60
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function typeMatches(value, expectedType) {
|
|
64
|
+
if (expectedType === "array") return Array.isArray(value);
|
|
65
|
+
if (expectedType === "object") return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
66
|
+
if (expectedType === "number") return typeof value === "number" && Number.isFinite(value);
|
|
67
|
+
return typeof value === expectedType;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function validateValue(schema, value, path = "artifact") {
|
|
71
|
+
const errors = [];
|
|
72
|
+
|
|
73
|
+
if (schema.type && !typeMatches(value, schema.type)) {
|
|
74
|
+
errors.push(`${path} must be ${schema.type}`);
|
|
75
|
+
return errors;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Object.hasOwn(schema, "const") && value !== schema.const) {
|
|
79
|
+
errors.push(`${path} must be ${JSON.stringify(schema.const)}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
83
|
+
errors.push(`${path} must be one of: ${schema.enum.join(", ")}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (schema.type === "string" && schema.minLength && value.length < schema.minLength) {
|
|
87
|
+
errors.push(`${path} must be at least ${schema.minLength} character(s)`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (schema.type === "string" && schema.maxLength && value.length > schema.maxLength) {
|
|
91
|
+
errors.push(`${path} must be at most ${schema.maxLength} character(s)`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (schema.type === "string" && schema.pattern && !(new RegExp(schema.pattern).test(value))) {
|
|
95
|
+
errors.push(`${path} must match pattern ${schema.pattern}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (schema.type === "number") {
|
|
99
|
+
if (Object.hasOwn(schema, "minimum") && value < schema.minimum) {
|
|
100
|
+
errors.push(`${path} must be >= ${schema.minimum}`);
|
|
101
|
+
}
|
|
102
|
+
if (Object.hasOwn(schema, "maximum") && value > schema.maximum) {
|
|
103
|
+
errors.push(`${path} must be <= ${schema.maximum}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (schema.allOf) {
|
|
108
|
+
for (const branchSchema of schema.allOf) {
|
|
109
|
+
errors.push(...validateValue(branchSchema, value, path));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (schema.anyOf) {
|
|
114
|
+
const branchErrors = schema.anyOf.map((branchSchema) => validateValue(branchSchema, value, path));
|
|
115
|
+
if (!branchErrors.some((candidateErrors) => candidateErrors.length === 0)) {
|
|
116
|
+
const detail = [...new Set(branchErrors.flat())].join("; ");
|
|
117
|
+
errors.push(`${path} must match at least one anyOf schema${detail ? `: ${detail}` : ""}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (schema.contains && Array.isArray(value)) {
|
|
122
|
+
const matchCount = value.filter((item, index) => validateValue(schema.contains, item, `${path}[${index}]`).length === 0).length;
|
|
123
|
+
const minContains = schema.minContains ?? 1;
|
|
124
|
+
if (matchCount < minContains) {
|
|
125
|
+
const expectedName = schema.contains?.properties?.name?.const;
|
|
126
|
+
const suffix = expectedName ? ` (${expectedName})` : "";
|
|
127
|
+
errors.push(`${path} must contain at least ${minContains} item(s) matching contains schema${suffix}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (schema.type === "array") {
|
|
132
|
+
if (schema.minItems && value.length < schema.minItems) {
|
|
133
|
+
errors.push(`${path} must contain at least ${schema.minItems} item(s)`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (schema.items) {
|
|
137
|
+
value.forEach((item, index) => {
|
|
138
|
+
errors.push(...validateValue(schema.items, item, `${path}[${index}]`));
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (schema.type === "object") {
|
|
144
|
+
for (const requiredKey of schema.required ?? []) {
|
|
145
|
+
if (!Object.hasOwn(value, requiredKey)) {
|
|
146
|
+
errors.push(`${path}.${requiredKey} is required`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (const [key, propertySchema] of Object.entries(schema.properties ?? {})) {
|
|
151
|
+
if (Object.hasOwn(value, key)) {
|
|
152
|
+
errors.push(...validateValue(propertySchema, value[key], `${path}.${key}`));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (schema.additionalProperties === false) {
|
|
157
|
+
const allowed = new Set(Object.keys(schema.properties ?? {}));
|
|
158
|
+
for (const key of Object.keys(value)) {
|
|
159
|
+
if (!allowed.has(key)) {
|
|
160
|
+
errors.push(`${path}.${key} is not allowed`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return errors;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function validateArtifact({ schemaPath, artifactPath }) {
|
|
170
|
+
const schemaResult = await readJson(schemaPath, "schema");
|
|
171
|
+
if (!schemaResult.ok) return { ok: false, errors: schemaResult.errors };
|
|
172
|
+
|
|
173
|
+
const artifactResult = await readJson(artifactPath, "artifact");
|
|
174
|
+
if (!artifactResult.ok) return { ok: false, errors: artifactResult.errors };
|
|
175
|
+
|
|
176
|
+
const errors = validateValue(schemaResult.value, artifactResult.value);
|
|
177
|
+
return { ok: errors.length === 0, errors };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function main() {
|
|
181
|
+
const result = await validateArtifact(parseArgs(process.argv.slice(2)));
|
|
182
|
+
|
|
183
|
+
if (!result.ok) {
|
|
184
|
+
for (const error of result.errors) {
|
|
185
|
+
console.error(error);
|
|
186
|
+
}
|
|
187
|
+
process.exitCode = 1;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log("Artifact is valid.");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
195
|
+
await main();
|
|
196
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { validateLaunchEnvelope } from "../lib/internal/launch-envelope.mjs";
|
|
7
|
+
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
const options = {};
|
|
10
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
11
|
+
const arg = argv[index];
|
|
12
|
+
const value = argv[index + 1];
|
|
13
|
+
if (arg === "--envelope") {
|
|
14
|
+
options.envelopePath = value;
|
|
15
|
+
index += 1;
|
|
16
|
+
} else if (arg === "--run-dir") {
|
|
17
|
+
options.runDir = value;
|
|
18
|
+
index += 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return options;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function validateLaunchEnvelopeFile({
|
|
25
|
+
packageRoot = join(import.meta.dirname, ".."),
|
|
26
|
+
runDir,
|
|
27
|
+
envelopePath
|
|
28
|
+
}) {
|
|
29
|
+
if (!envelopePath) {
|
|
30
|
+
return { ok: false, errors: ["Missing --envelope argument"] };
|
|
31
|
+
}
|
|
32
|
+
const envelope = JSON.parse(await readFile(envelopePath, "utf8"));
|
|
33
|
+
return validateLaunchEnvelope({ packageRoot, runDir, envelope });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function main() {
|
|
37
|
+
const result = await validateLaunchEnvelopeFile(parseArgs(process.argv.slice(2)));
|
|
38
|
+
if (!result.ok) {
|
|
39
|
+
for (const error of result.errors) {
|
|
40
|
+
console.error(error);
|
|
41
|
+
}
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log("Launch envelope is valid.");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
49
|
+
await main();
|
|
50
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { validateRegistryConsistency } from "../lib/internal/multi-agent-contracts.mjs";
|
|
7
|
+
|
|
8
|
+
const defaultPackageRoot = join(import.meta.dirname, "..");
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const options = {};
|
|
12
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
13
|
+
const arg = argv[index];
|
|
14
|
+
const value = argv[index + 1];
|
|
15
|
+
if (arg === "--package-root") {
|
|
16
|
+
options.packageRoot = value;
|
|
17
|
+
index += 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return options;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function validateRegistries({
|
|
24
|
+
packageRoot = defaultPackageRoot,
|
|
25
|
+
requireWorkflowFiles = existsSync(join(packageRoot, "workflows"))
|
|
26
|
+
} = {}) {
|
|
27
|
+
return validateRegistryConsistency({
|
|
28
|
+
root: packageRoot,
|
|
29
|
+
workflowManifestPath: join(packageRoot, "contracts", "workflow-manifest.json"),
|
|
30
|
+
capabilityRegistryPath: join(packageRoot, "contracts", "capability-registry.json"),
|
|
31
|
+
actionRegistryPath: join(packageRoot, "contracts", "action-registry.json"),
|
|
32
|
+
requireWorkflowFiles
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function main() {
|
|
37
|
+
const result = await validateRegistries(parseArgs(process.argv.slice(2)));
|
|
38
|
+
if (!result.ok) {
|
|
39
|
+
for (const error of result.errors) {
|
|
40
|
+
console.error(error);
|
|
41
|
+
}
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log("Registries are consistent.");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
49
|
+
await main();
|
|
50
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { validateRegistries } from "./validate-registries.mjs";
|
|
6
|
+
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
const options = {};
|
|
9
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
10
|
+
const arg = argv[index];
|
|
11
|
+
const value = argv[index + 1];
|
|
12
|
+
if (arg === "--package-root") {
|
|
13
|
+
options.packageRoot = value;
|
|
14
|
+
index += 1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return options;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function validateWorkflowManifest(options = {}) {
|
|
21
|
+
return validateRegistries(options);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
const result = await validateWorkflowManifest(parseArgs(process.argv.slice(2)));
|
|
26
|
+
if (!result.ok) {
|
|
27
|
+
for (const error of result.errors) {
|
|
28
|
+
console.error(error);
|
|
29
|
+
}
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log("Workflow manifest is valid.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
37
|
+
await main();
|
|
38
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const skillRoot = resolve(scriptDir, "..");
|
|
12
|
+
const installedPluginRoot = resolve(skillRoot, "..");
|
|
13
|
+
const defaultRoot = existsSync(join(installedPluginRoot, ".codex-plugin", "plugin.json")) ? installedPluginRoot : skillRoot;
|
|
14
|
+
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
const args = {};
|
|
17
|
+
|
|
18
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
19
|
+
if (argv[index] === "--plugin-root") {
|
|
20
|
+
args.pluginRoot = argv[index + 1];
|
|
21
|
+
index += 1;
|
|
22
|
+
} else if (argv[index] === "--json") {
|
|
23
|
+
args.json = true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return args;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function readTextIfExists(path) {
|
|
31
|
+
if (!existsSync(path)) {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
return (await readFile(path, "utf8")).trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function readJson(path) {
|
|
38
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function gitCommit(root) {
|
|
42
|
+
try {
|
|
43
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "--short", "HEAD"], { cwd: root });
|
|
44
|
+
return stdout.trim();
|
|
45
|
+
} catch {
|
|
46
|
+
return "unknown";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function runtimeRootFor(root) {
|
|
51
|
+
return existsSync(join(root, "storewright", "scripts")) ? join(root, "storewright") : root;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function stageWorkflowCapabilities(runtimeRoot) {
|
|
55
|
+
const workflowRoot = join(runtimeRoot, "workflows");
|
|
56
|
+
const stagesRoot = join(workflowRoot, "stages");
|
|
57
|
+
const specsRoot = join(workflowRoot, "stage-specs");
|
|
58
|
+
const gateSpecsRoot = join(workflowRoot, "gate-specs");
|
|
59
|
+
const capabilities = [];
|
|
60
|
+
|
|
61
|
+
if (!existsSync(stagesRoot)) {
|
|
62
|
+
return capabilities;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const stageEntries = await readdir(stagesRoot, { withFileTypes: true });
|
|
66
|
+
capabilities.push(...stageEntries
|
|
67
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
|
|
68
|
+
.map((entry) => `workflows/stages/${entry.name}`)
|
|
69
|
+
.sort());
|
|
70
|
+
|
|
71
|
+
if (existsSync(specsRoot)) {
|
|
72
|
+
const specEntries = await readdir(specsRoot, { withFileTypes: true });
|
|
73
|
+
capabilities.push(...specEntries
|
|
74
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
75
|
+
.map((entry) => `workflows/stage-specs/${entry.name}`)
|
|
76
|
+
.sort());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (existsSync(gateSpecsRoot)) {
|
|
80
|
+
const gateSpecEntries = await readdir(gateSpecsRoot, { withFileTypes: true });
|
|
81
|
+
capabilities.push(...gateSpecEntries
|
|
82
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
83
|
+
.map((entry) => `workflows/gate-specs/${entry.name}`)
|
|
84
|
+
.sort());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return capabilities;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function directoryFileCapabilities(runtimeRoot, relativeDir, extension) {
|
|
91
|
+
const dir = join(runtimeRoot, relativeDir);
|
|
92
|
+
if (!existsSync(dir)) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
96
|
+
return entries
|
|
97
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(extension))
|
|
98
|
+
.map((entry) => `${relativeDir}/${entry.name}`)
|
|
99
|
+
.sort();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function capabilityScripts(root) {
|
|
103
|
+
const runtimeRoot = runtimeRootFor(root);
|
|
104
|
+
const scriptsRoot = join(runtimeRoot, "scripts");
|
|
105
|
+
const cliBin = join(runtimeRoot, "bin", "storewright.mjs");
|
|
106
|
+
const workflowContract = join(runtimeRoot, "workflows", "official-build.md");
|
|
107
|
+
const setupSkill = join(runtimeRoot, "skills", "storewright-setup", "SKILL.md");
|
|
108
|
+
const capabilities = existsSync(workflowContract) ? ["workflows/official-build.md"] : [];
|
|
109
|
+
const publicScripts = [
|
|
110
|
+
"audit-run-state.mjs",
|
|
111
|
+
"execute-shopify-operation.mjs",
|
|
112
|
+
"generate-image-assets-openai.mjs",
|
|
113
|
+
"generate-media-assets.mjs",
|
|
114
|
+
"inspect-media-files.mjs",
|
|
115
|
+
"install-agent-profiles.mjs",
|
|
116
|
+
"init-run-state.mjs",
|
|
117
|
+
"prepare-launch-envelope.mjs",
|
|
118
|
+
"shopify-access-preflight.mjs",
|
|
119
|
+
"upload-shopify-media.mjs",
|
|
120
|
+
"validate-agent-discovery.mjs",
|
|
121
|
+
"validate-agent-report.mjs",
|
|
122
|
+
"validate-artifact.mjs",
|
|
123
|
+
"validate-launch-envelope.mjs",
|
|
124
|
+
"validate-registries.mjs",
|
|
125
|
+
"validate-workflow-manifest.mjs",
|
|
126
|
+
"version.mjs"
|
|
127
|
+
];
|
|
128
|
+
const publicSchemas = [
|
|
129
|
+
"shopify-operation-request.schema.json"
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
return [
|
|
133
|
+
...capabilities,
|
|
134
|
+
...(await directoryFileCapabilities(runtimeRoot, "contracts", ".json")),
|
|
135
|
+
...publicSchemas
|
|
136
|
+
.filter((schema) => existsSync(join(runtimeRoot, "schemas", schema)))
|
|
137
|
+
.map((schema) => `schemas/${schema}`),
|
|
138
|
+
...(existsSync(setupSkill) ? ["skills/storewright-setup/SKILL.md"] : []),
|
|
139
|
+
...(await stageWorkflowCapabilities(runtimeRoot)),
|
|
140
|
+
...(existsSync(cliBin) ? ["bin/storewright.mjs"] : []),
|
|
141
|
+
...publicScripts.filter((script) => existsSync(join(scriptsRoot, script)))
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function getVersionInfo({ pluginRoot: explicitPluginRoot = defaultRoot } = {}) {
|
|
146
|
+
const root = resolve(explicitPluginRoot);
|
|
147
|
+
const runtimeRoot = runtimeRootFor(root);
|
|
148
|
+
const manifestPath = join(root, ".codex-plugin", "plugin.json");
|
|
149
|
+
const packageJsonPath = join(runtimeRoot, "package.json");
|
|
150
|
+
const manifest = existsSync(manifestPath) ? await readJson(manifestPath) : null;
|
|
151
|
+
const packageJson = existsSync(packageJsonPath) ? await readJson(packageJsonPath) : null;
|
|
152
|
+
const pluginVersion = await readTextIfExists(existsSync(manifestPath) ? join(root, "VERSION") : join(runtimeRoot, "VERSION"));
|
|
153
|
+
const skillVersion = existsSync(manifestPath) ? await readTextIfExists(join(runtimeRoot, "VERSION")) : "";
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
name: manifest?.name ?? packageJson?.name ?? "storewright",
|
|
157
|
+
displayName: manifest?.interface?.displayName ?? (packageJson?.name === "@storewright/cli" ? "Storewright CLI" : packageJson?.name ?? "Storewright"),
|
|
158
|
+
manifestVersion: manifest?.version ?? "",
|
|
159
|
+
packageVersion: packageJson?.version ?? "",
|
|
160
|
+
pluginVersion,
|
|
161
|
+
skillVersion,
|
|
162
|
+
gitCommit: await gitCommit(root),
|
|
163
|
+
capabilities: await capabilityScripts(root)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function textOutput(info) {
|
|
168
|
+
return [
|
|
169
|
+
`${info.displayName} (${info.name})`,
|
|
170
|
+
`manifestVersion: ${info.manifestVersion}`,
|
|
171
|
+
`pluginVersion: ${info.pluginVersion || "missing"}`,
|
|
172
|
+
`skillVersion: ${info.skillVersion || "missing"}`,
|
|
173
|
+
`gitCommit: ${info.gitCommit}`,
|
|
174
|
+
"capabilities:",
|
|
175
|
+
...info.capabilities.map((capability) => `- ${capability}`)
|
|
176
|
+
].join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function main() {
|
|
180
|
+
try {
|
|
181
|
+
const args = parseArgs(process.argv.slice(2));
|
|
182
|
+
const info = await getVersionInfo({ pluginRoot: args.pluginRoot });
|
|
183
|
+
console.log(args.json ? JSON.stringify(info, null, 2) : textOutput(info));
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error(error.message);
|
|
186
|
+
process.exitCode = 1;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
191
|
+
await main();
|
|
192
|
+
}
|