@cimplify/cli 0.6.10 → 0.6.12
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/dist/{add-GDHA7MKM.mjs → add-UZRM3FSU.mjs} +1 -1
- package/dist/{chunk-ZTKQOLAC.mjs → chunk-A2L5IV57.mjs} +1 -1
- package/dist/{chunk-R3FDBXR6.mjs → chunk-CLNS2NBR.mjs} +4 -1
- package/dist/{chunk-5L6LJE6I.mjs → chunk-E7P6GL73.mjs} +4 -1
- package/dist/chunk-VOQJ7GYE.mjs +5707 -0
- package/dist/{deploy-7BPO5BNB.mjs → deploy-YVCYHNSS.mjs} +48 -19
- package/dist/dispatcher.mjs +13 -11
- package/dist/{doctor-SSSYBYCL.mjs → doctor-Q6DWYG4D.mjs} +2 -2
- package/dist/{explain-VG7XUP62.mjs → explain-RA3PQCUF.mjs} +22 -1
- package/dist/inspect-CGYX4DDF.mjs +411 -0
- package/dist/{introspect-HYJ6VI3U.mjs → introspect-HKNLBZ73.mjs} +2 -2
- package/dist/{list-NQP4SU5K.mjs → list-U67HVMX5.mjs} +1 -1
- package/dist/{repo-WOBWKEAO.mjs → repo-KNQMSPVV.mjs} +1 -1
- package/dist/{update-HHRCPKSU.mjs → update-UT46Z6X4.mjs} +1 -1
- package/package.json +4 -1
- package/templates/storefront-auto/bun.lock +1167 -0
- package/templates/storefront-auto/package.json +1 -1
- package/templates/storefront-auto/tsconfig.json +24 -6
- package/templates/storefront-bakery/bun.lock +1167 -0
- package/templates/storefront-bakery/package.json +1 -1
- package/templates/storefront-bakery/tsconfig.json +24 -6
- package/templates/storefront-fashion/bun.lock +1176 -0
- package/templates/storefront-fashion/package.json +1 -1
- package/templates/storefront-fashion/tsconfig.json +24 -6
- package/templates/storefront-grocery/bun.lock +1167 -0
- package/templates/storefront-grocery/package.json +1 -1
- package/templates/storefront-grocery/tsconfig.json +24 -6
- package/templates/storefront-pharmacy/bun.lock +1167 -0
- package/templates/storefront-pharmacy/package.json +1 -1
- package/templates/storefront-pharmacy/tsconfig.json +24 -6
- package/templates/storefront-restaurant/bun.lock +1167 -0
- package/templates/storefront-restaurant/package.json +1 -1
- package/templates/storefront-restaurant/tsconfig.json +24 -6
- package/templates/storefront-retail/bun.lock +1167 -0
- package/templates/storefront-retail/package.json +1 -1
- package/templates/storefront-retail/tsconfig.json +24 -6
- package/templates/storefront-services/bun.lock +1167 -0
- package/templates/storefront-services/package.json +1 -1
- package/templates/storefront-services/tsconfig.json +24 -6
- package/dist/chunk-EKJ6T66O.mjs +0 -5707
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { gitDetectRoot, gitStatusPorcelain, gitCurrentBranch, gitCurrentSha, gitGetRemoteUrl,
|
|
2
|
+
import { isFreestyleRemote, gitDetectRoot, gitStatusPorcelain, gitCurrentBranch, gitCurrentSha, gitGetRemoteUrl, gitPushToUrl, gitPush } from './chunk-K5464A3L.mjs';
|
|
3
3
|
import { pollDeployment } from './chunk-VTR5R5NQ.mjs';
|
|
4
|
-
import { fetchCloneToken } from './chunk-
|
|
4
|
+
import { fetchRepoRecord, fetchCloneToken } from './chunk-CLNS2NBR.mjs';
|
|
5
5
|
import { promptYesNo } from './chunk-ITAFAORS.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import { REPO_PROVIDER, ENV_SCOPE, DEPLOY_TRIGGER, DEPLOYMENT_STATUS, TOKEN_PURPOSE } from './chunk-MXYUAJEW.mjs';
|
|
7
7
|
import { parseArgs, flagBool, flagString } from './chunk-C4M3DXKC.mjs';
|
|
8
8
|
import { ApiClient } from './chunk-MAOO6ZZ5.mjs';
|
|
9
9
|
import { readAuth, readProjectLink, writeProjectState } from './chunk-UBAI443T.mjs';
|
|
10
|
-
import { CliError, CLI_ERROR_CODE,
|
|
10
|
+
import { CliError, CLI_ERROR_CODE, info, dim, step, success, result, EXIT_CODE } from './chunk-E2T2SBP5.mjs';
|
|
11
11
|
|
|
12
12
|
// src/commands/deploy.ts
|
|
13
13
|
var FLAG_PROD = "prod";
|
|
@@ -18,6 +18,48 @@ var FLAG_NO_POLL = "no-poll";
|
|
|
18
18
|
function deployEndpoint(businessId, projectId) {
|
|
19
19
|
return `/v1/businesses/${encodeURIComponent(businessId)}/projects/${encodeURIComponent(projectId)}/deploy`;
|
|
20
20
|
}
|
|
21
|
+
function resolvePushStrategy(originUrl, linkRemoteUrl, repo) {
|
|
22
|
+
if (isFreestyleRemote(originUrl) || isFreestyleRemote(linkRemoteUrl)) {
|
|
23
|
+
return { kind: "managed" };
|
|
24
|
+
}
|
|
25
|
+
if (repo) {
|
|
26
|
+
if (repo.provider === REPO_PROVIDER.FREESTYLE || isFreestyleRemote(repo.remote_url)) {
|
|
27
|
+
return { kind: "managed" };
|
|
28
|
+
}
|
|
29
|
+
if (!originUrl) {
|
|
30
|
+
return {
|
|
31
|
+
kind: "error",
|
|
32
|
+
message: `This project uses a connected ${repo.provider} repo but no local 'origin' remote is set.
|
|
33
|
+
Add it: git remote add origin ${repo.remote_url}
|
|
34
|
+
Then re-run cimplify deploy.`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return { kind: "origin" };
|
|
38
|
+
}
|
|
39
|
+
if (!originUrl) {
|
|
40
|
+
return {
|
|
41
|
+
kind: "error",
|
|
42
|
+
message: "No git remote configured and no repo attached to this project.\nProvision a managed repo: cimplify repo provision\nOr connect your own: cimplify repo connect <url>"
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return { kind: "origin" };
|
|
46
|
+
}
|
|
47
|
+
async function pushForDeploy(client, businessId, projectId, root, branch, linkRemoteUrl) {
|
|
48
|
+
const originUrl = await gitGetRemoteUrl(root);
|
|
49
|
+
const repo = isFreestyleRemote(originUrl) || isFreestyleRemote(linkRemoteUrl) ? null : await fetchRepoRecord(client, businessId, projectId);
|
|
50
|
+
const strategy = resolvePushStrategy(originUrl, linkRemoteUrl, repo);
|
|
51
|
+
if (strategy.kind === "error") {
|
|
52
|
+
throw new CliError(CLI_ERROR_CODE.GIT_ERROR, strategy.message);
|
|
53
|
+
}
|
|
54
|
+
if (strategy.kind === "managed") {
|
|
55
|
+
step(`Pushing ${branch} (managed remote \u2014 minting short-TTL clone token)`);
|
|
56
|
+
const token = await fetchCloneToken(client, businessId, projectId, TOKEN_PURPOSE.EDITOR);
|
|
57
|
+
await gitPushToUrl(root, token.clone_url, branch);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
step(`Pushing ${branch} to origin`);
|
|
61
|
+
await gitPush(root, branch);
|
|
62
|
+
}
|
|
21
63
|
async function run(argv) {
|
|
22
64
|
const args = parseArgs(argv);
|
|
23
65
|
const auth = await readAuth();
|
|
@@ -47,20 +89,7 @@ async function run(argv) {
|
|
|
47
89
|
const localSha = await gitCurrentSha(root);
|
|
48
90
|
const gitRef = explicitRef ?? localSha;
|
|
49
91
|
if (!flagBool(args, FLAG_NO_PUSH)) {
|
|
50
|
-
|
|
51
|
-
if (isFreestyleRemote(originUrl)) {
|
|
52
|
-
step(`Pushing ${branch} (managed remote \u2014 minting short-TTL clone token)`);
|
|
53
|
-
const token = await fetchCloneToken(
|
|
54
|
-
client,
|
|
55
|
-
link.businessId,
|
|
56
|
-
link.projectId,
|
|
57
|
-
TOKEN_PURPOSE.EDITOR
|
|
58
|
-
);
|
|
59
|
-
await gitPushToUrl(root, token.clone_url, branch);
|
|
60
|
-
} else {
|
|
61
|
-
step(`Pushing ${branch} to origin`);
|
|
62
|
-
await gitPush(root, branch);
|
|
63
|
-
}
|
|
92
|
+
await pushForDeploy(client, link.businessId, link.projectId, root, branch, link.remoteUrl);
|
|
64
93
|
} else {
|
|
65
94
|
info(dim("Skipping git push (--no-push)"));
|
|
66
95
|
}
|
|
@@ -129,4 +158,4 @@ async function run(argv) {
|
|
|
129
158
|
process.exitCode = EXIT_CODE.ERROR;
|
|
130
159
|
}
|
|
131
160
|
|
|
132
|
-
export { run as default };
|
|
161
|
+
export { run as default, resolvePushStrategy };
|
package/dist/dispatcher.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { TEMPLATES } from './chunk-
|
|
3
|
-
import { package_default } from './chunk-
|
|
2
|
+
import { TEMPLATES } from './chunk-VOQJ7GYE.mjs';
|
|
3
|
+
import { package_default } from './chunk-E7P6GL73.mjs';
|
|
4
4
|
|
|
5
5
|
// src/dispatcher.ts
|
|
6
6
|
var VERSION = package_default.version ?? "unknown";
|
|
@@ -19,6 +19,7 @@ Project:
|
|
|
19
19
|
cimplify link <project-id> Link CWD to a Cimplify project (writes .cimplify/project.json)
|
|
20
20
|
cimplify unlink Remove the local project link
|
|
21
21
|
cimplify introspect Snapshot the current storefront: auth, link, brand, mock, env, git
|
|
22
|
+
cimplify inspect [catalogue] Read-only snapshot of the live catalogue (products + collections + gaps)
|
|
22
23
|
cimplify doctor [--offline] Run pre-flight checks; verdicts + fix hints (exit 1 on any fail)
|
|
23
24
|
cimplify explain [topic] Print canonical guidance on cart, products, bundles, errors, \u2026
|
|
24
25
|
cimplify assets upload <dir> Upload storefront brand assets to the Cimplify CDN
|
|
@@ -129,7 +130,7 @@ var COMMANDS = {
|
|
|
129
130
|
projects: () => import('./projects-JSEC2YCX.mjs'),
|
|
130
131
|
link: () => import('./link-DZSILT5N.mjs'),
|
|
131
132
|
unlink: () => import('./unlink-RFK74SFP.mjs'),
|
|
132
|
-
deploy: () => import('./deploy-
|
|
133
|
+
deploy: () => import('./deploy-YVCYHNSS.mjs'),
|
|
133
134
|
cancel: () => import('./cancel-HANLRA6Q.mjs'),
|
|
134
135
|
rollback: () => import('./rollback-DD4RNRFM.mjs'),
|
|
135
136
|
env: () => import('./env-7ISJ73YI.mjs'),
|
|
@@ -137,15 +138,16 @@ var COMMANDS = {
|
|
|
137
138
|
logs: () => import('./logs-YNN2PQ24.mjs'),
|
|
138
139
|
status: () => import('./status-JSYXM5RT.mjs'),
|
|
139
140
|
dev: () => import('./dev-ONW2S77K.mjs'),
|
|
140
|
-
introspect: () => import('./introspect-
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
introspect: () => import('./introspect-HKNLBZ73.mjs'),
|
|
142
|
+
inspect: () => import('./inspect-CGYX4DDF.mjs'),
|
|
143
|
+
doctor: () => import('./doctor-Q6DWYG4D.mjs'),
|
|
144
|
+
explain: () => import('./explain-RA3PQCUF.mjs'),
|
|
143
145
|
assets: () => import('./assets-EBEMMENZ.mjs'),
|
|
144
|
-
repo: () => import('./repo-
|
|
145
|
-
list: () => import('./list-
|
|
146
|
-
add: () => import('./add-
|
|
147
|
-
update: () => import('./update-
|
|
148
|
-
upgrade: () => import('./update-
|
|
146
|
+
repo: () => import('./repo-KNQMSPVV.mjs'),
|
|
147
|
+
list: () => import('./list-U67HVMX5.mjs'),
|
|
148
|
+
add: () => import('./add-UZRM3FSU.mjs'),
|
|
149
|
+
update: () => import('./update-UT46Z6X4.mjs'),
|
|
150
|
+
upgrade: () => import('./update-UT46Z6X4.mjs'),
|
|
149
151
|
"auth-step-up": () => import('./auth-step-up-BIUYQJP6.mjs')
|
|
150
152
|
};
|
|
151
153
|
var COMMAND_PREFIXES = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { gatherIntrospection } from './chunk-
|
|
2
|
+
import { gatherIntrospection } from './chunk-A2L5IV57.mjs';
|
|
3
3
|
import './chunk-K5464A3L.mjs';
|
|
4
4
|
import './chunk-DBZ3UOQ2.mjs';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-E7P6GL73.mjs';
|
|
6
6
|
import { parseArgs, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
7
7
|
import { ApiClient } from './chunk-MAOO6ZZ5.mjs';
|
|
8
8
|
import { readAuthOrNull } from './chunk-UBAI443T.mjs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { package_default } from './chunk-
|
|
2
|
+
import { package_default } from './chunk-E7P6GL73.mjs';
|
|
3
3
|
import { parseArgs } from './chunk-C4M3DXKC.mjs';
|
|
4
4
|
import { bold, dim, info, result, CliError, CLI_ERROR_CODE } from './chunk-E2T2SBP5.mjs';
|
|
5
5
|
|
|
@@ -138,6 +138,27 @@ var TOPICS = [
|
|
|
138
138
|
"source_url": "https://cimplify.dev/docs/cli#exit-codes",
|
|
139
139
|
"body": "## Exit codes\nDistinct per error class so callers can branch on `$?` without parsing output:\n\n| Code | Name | Meaning |\n| --- | --- | --- |\n| `0` | OK | Success. For `deploy`, deployment is `active`. |\n| `1` | FAILED | Generic failure (build failed, deploy errored). |\n| `2` | SUPERSEDED | A newer push raced this deploy; usually fine. |\n| `3` | ABORTED | User canceled (Ctrl-C) or `cimplify_cancel_deployment`. |\n| `4` | NOT_LINKED | `.cimplify/project.json` missing. Run `cimplify link <id>`. |\n| `7` | INTERACTIVE_REQUIRED | A prompt was needed but `--yes` not passed and stdin is non-TTY. |\n| `9` | NOT_FOUND | Resource (project, domain, deploy) does not exist. |\n| `10` | NETWORK_ERROR | Could not reach `api.cimplify.io`. |\n| `12` | TIMEOUT | Long-poll exceeded its window; the underlying job may still be running. |\n| `20` | NOT_LOGGED_IN | No saved token. Run `cimplify login` or pass `--api-key`. |\n| `21` | AUTH_FAILED | Token expired or revoked. Re-login. |\n| `30` | INVALID_INPUT | Argument/flag validation failed. |\n\nFull list in `@cimplify/cli/errors`.\n"
|
|
140
140
|
},
|
|
141
|
+
{
|
|
142
|
+
"name": "inspect-json",
|
|
143
|
+
"title": 'Inspect \u2014 The JSON envelope (locked contract, `schema_version: "1"`)',
|
|
144
|
+
"description": "The locked JSON envelope cimplify inspect emits",
|
|
145
|
+
"source_url": "https://cimplify.dev/docs/cli/inspect#the-json-envelope-locked-contract-schema_version-1",
|
|
146
|
+
"body": '## The JSON envelope (locked contract, `schema_version: "1"`)\n```jsonc\n{\n "ok": true,\n "schema_version": "1",\n "data": {\n "business": {\n "handle": "bakeryco",\n "name": "Bakery & Co.",\n "currencies": ["USD", "GHS"],\n "locations": 2\n },\n "totals": {\n "products": 142,\n "collections": 8,\n "categories": 12,\n "distinct_tags": 24\n },\n "gaps": {\n "no_image": 11,\n "no_description": 7,\n "no_price": 0,\n "no_collection": 23,\n "no_tags": 56,\n "empty_collections": [{ "id": "col_seasonal", "name": "Seasonal" }],\n "empty_categories": [{ "id": "cat_specials", "name": "Specials" }]\n },\n "products": {\n "sample": [\n {\n "id": "prod_abc1",\n "name": "Croissant",\n "slug": "croissant",\n "price": { "amount": "3.50", "currency": "USD" },\n "has_image": true,\n "has_description": true,\n "tags": ["vegan", "bestseller"],\n "collection_ids": ["col_featured"],\n "category_ids": ["cat_breakfast"],\n "variant_count": 3\n }\n ],\n "next_cursor": "eyJpZCI6IjE5In0",\n "returned": 20\n },\n "collections": [\n {\n "id": "col_featured",\n "name": "Featured",\n "slug": "featured",\n "product_count": 12,\n "sample_product_ids": ["prod_abc1", "prod_abc2", "prod_abc3"]\n }\n ],\n "categories": {\n "tree": [\n { "id": "cat_breakfast", "name": "Breakfast", "slug": "breakfast", "product_count": 24, "children": [] }\n ]\n },\n "tags": {\n "top": [{ "tag": "vegan", "count": 18 }, { "tag": "bestseller", "count": 12 }]\n }\n },\n "meta": {\n "fetched_at": "2026-05-23T10:15:32Z",\n "source": "hosted",\n "sampled": true,\n "sample_size": 20,\n "gaps_method": "sample",\n "shape_hints": ["medium_catalogue", "uses_collections", "has_empty_collection", "untagged_majority", "has_image_gaps"],\n "suggestions": [\n { "kind": "fix_no_image", "count": 11 },\n { "kind": "fix_no_collection", "count": 23 },\n { "kind": "merchandise_empty_collection", "collection_id": "col_seasonal", "name": "Seasonal" }\n ],\n "suggested_commands": ["cimplify inspect catalogue --all"]\n }\n}\n```\n\n### Error envelope\n\n```jsonc\n{\n "ok": false,\n "schema_version": "1",\n "error": {\n "code": "NO_PUBLIC_KEY",\n "message": "Set NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY in .env or pass --public-key.",\n "remediation": "Get one at desk.cimplify.io/settings/api."\n }\n}\n```\n\n### Field rules\n\n- `ok` is always present.\n- `schema_version` is a contract over the whole document; bump on **removal**, **rename**, or **type change** of any field, `shape_hints` token, or `suggestions[].kind`. **Adding** is non-breaking.\n- `data.gaps.empty_collections` / `empty_categories` are always arrays (return `[]`, never omit).\n- `data.products.sample` is **always page-ordered** (never randomized).\n- `next_cursor` is `null` when the walk is exhausted.\n- `meta.source` is `"hosted"` or `"mock"`. Locked.\n- `meta.gaps_method` is `"sample"` or `"exact"`. Locked.\n- `meta.shape_hints` is **sorted alphabetically** (deterministic, diffable).\n- `meta.suggestions` is ordered by impact (most actionable first).\n- `meta.fetched_at` is the only timestamp; nothing in `data` carries time.\n'
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "shape-hints",
|
|
150
|
+
"title": "Inspect \u2014 `shape_hints` vocabulary",
|
|
151
|
+
"description": "The canonical shape_hints vocabulary for cimplify inspect",
|
|
152
|
+
"source_url": "https://cimplify.dev/docs/cli/inspect#shape_hints-vocabulary",
|
|
153
|
+
"body": "## `shape_hints` vocabulary\nFlat string tokens for agents to branch on. Adding tokens is non-breaking; removing/renaming bumps `schema_version`.\n\n### Catalogue size\n\n| Token | Meaning |\n| --- | --- |\n| `empty_catalogue` | 0 products. Whole storefront needs an empty-state. |\n| `tiny_catalogue` | 1\u20139 products. Skip search, deep filters. |\n| `small_catalogue` | 10\u201399 products. Default UX. |\n| `medium_catalogue` | 100\u2013999 products. Search nice-to-have. |\n| `large_catalogue` | 1000+ products. Search + facets required. |\n\n### Feature presence\n\n| Token | Meaning |\n| --- | --- |\n| `has_variants` | At least one product has variants \u2192 variant picker UI. |\n| `has_addons` | At least one product has add-on options \u2192 modifier UI. |\n| `has_subscriptions` | Billing plans exist \u2192 recurring pricing toggle. |\n| `has_composites` | Bundle / composite products exist. |\n| `has_recipes` | Products linked to `ProductComponent` (restaurant / manufacturing). |\n| `has_time_profiles` | Products have time-of-day windows \u2192 hours-aware UI. |\n| `has_input_fields` | Buyer-supplied inputs (engraving, message). |\n| `has_custom_attributes` | Custom attribute schemas defined. |\n| `per_location_pricing` | Prices vary by location. |\n| `multi_currency` | >1 currency configured. |\n| `multi_location` | >1 location configured. |\n\n### Merchandising state\n\n| Token | Meaning |\n| --- | --- |\n| `uses_collections` | \u22651 non-empty collection exists. |\n| `uses_categories` | \u22651 non-empty category. |\n| `has_empty_collection` | \u22651 collection has 0 products. |\n| `has_empty_category` | \u22651 category has 0 products. |\n| `untagged_majority` | >50% of products are untagged. |\n| `unmerchandised_majority` | >50% of products aren't in any collection. |\n\n### Data quality\n\n| Token | Meaning |\n| --- | --- |\n| `has_image_gaps` | \u22651 product missing image. |\n| `has_description_gaps` | \u22651 product missing description. |\n| `has_price_gaps` | \u22651 product missing price. |\n\n### Environment\n\n| Token | Meaning |\n| --- | --- |\n| `mock_data` | Reading from `cimplify-mock` (key isn't `cpk_live_*` / `cpk_test_*`). |\n"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"name": "inspect-suggestions",
|
|
157
|
+
"title": "Inspect \u2014 `suggestions[].kind` vocabulary",
|
|
158
|
+
"description": "The canonical suggestions[].kind vocabulary for cimplify inspect",
|
|
159
|
+
"source_url": "https://cimplify.dev/docs/cli/inspect#suggestionskind-vocabulary",
|
|
160
|
+
"body": '## `suggestions[].kind` vocabulary\nEach entry is a stable `kind` + typed payload. Copy lives here (and in `cimplify explain inspect-suggestions`) so agents don\'t invent phrasing.\n\n| Kind | Payload | Implied prompt |\n| --- | --- | --- |\n| `add_first_product` | `{}` | Catalogue is empty \u2014 seed or add a product. |\n| `add_first_collection` | `{}` | Products exist but no collections \u2014 group some. |\n| `fix_no_image` | `{ count }` | N products without images \u2014 add placeholders. |\n| `fix_no_description` | `{ count }` | N products lack descriptions \u2014 generate copy. |\n| `fix_no_price` | `{ count }` | N products have no price \u2014 open the editor. |\n| `fix_no_collection` | `{ count }` | N products not in any collection \u2014 suggest "Featured". |\n| `fix_no_tags` | `{ count }` | N untagged products \u2014 suggest tags from name/category. |\n| `merchandise_empty_collection` | `{ collection_id, name }` | Collection "X" is empty \u2014 pick products to feature. |\n| `merchandise_empty_category` | `{ category_id, name }` | Category "X" has no products \u2014 fill or remove. |\n| `tag_uncategorized_products` | `{ count }` | Majority untagged \u2014 bulk-tag from category. |\n| `enable_search_ui` | `{ product_count }` | Large catalogue \u2014 add search + filters. |\n'
|
|
161
|
+
},
|
|
141
162
|
{
|
|
142
163
|
"name": "agent-prompts",
|
|
143
164
|
"title": "Agent prompts",
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseEnvFile } from './chunk-DBZ3UOQ2.mjs';
|
|
3
|
+
import { parseArgs, flagString, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
4
|
+
import { CliError, CLI_ERROR_CODE, isJsonMode, result, info, bold, dim, yellow, green, red } from './chunk-E2T2SBP5.mjs';
|
|
5
|
+
import { promises } from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { createCimplifyClient } from '@cimplify/sdk';
|
|
8
|
+
|
|
9
|
+
var SCHEMA_VERSION = "1";
|
|
10
|
+
var HOSTED_URL = "https://storefronts.cimplify.io";
|
|
11
|
+
var MOCK_URL = "http://127.0.0.1:8787";
|
|
12
|
+
var DEFAULT_LIMIT = 20;
|
|
13
|
+
var COLLECTION_SAMPLE_SIZE = 3;
|
|
14
|
+
var TAG_TOP_N = 10;
|
|
15
|
+
var ENV_FILE_CANDIDATES = [".env.local", ".env", ".env.production", ".env.example"];
|
|
16
|
+
var PUBLIC_KEY_ENV = "NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY";
|
|
17
|
+
var FLAG_ALL = "all";
|
|
18
|
+
var FLAG_LIMIT = "limit";
|
|
19
|
+
var FLAG_CURSOR = "cursor";
|
|
20
|
+
var FLAG_PUBLIC_KEY = "public-key";
|
|
21
|
+
async function run(argv) {
|
|
22
|
+
const args = parseArgs(argv);
|
|
23
|
+
const sub = args.positional[0] ?? "catalogue";
|
|
24
|
+
if (sub !== "catalogue") {
|
|
25
|
+
throw new CliError(
|
|
26
|
+
CLI_ERROR_CODE.INVALID_INPUT,
|
|
27
|
+
`Unknown subcommand: ${sub}. Try \`cimplify inspect catalogue\`.`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
await runCatalogue(args);
|
|
31
|
+
}
|
|
32
|
+
async function runCatalogue(args) {
|
|
33
|
+
const publicKey = await resolvePublicKey(args);
|
|
34
|
+
const source = isHostedKey(publicKey) ? "hosted" : "mock";
|
|
35
|
+
const baseUrl = source === "hosted" ? HOSTED_URL : MOCK_URL;
|
|
36
|
+
const limit = parseLimit(flagString(args, FLAG_LIMIT));
|
|
37
|
+
const walkAll = flagBool(args, FLAG_ALL);
|
|
38
|
+
const cursor = flagString(args, FLAG_CURSOR);
|
|
39
|
+
const client = createCimplifyClient({
|
|
40
|
+
baseUrl,
|
|
41
|
+
publicKey,
|
|
42
|
+
suppressPublicKeyWarning: true
|
|
43
|
+
});
|
|
44
|
+
const data = await fetchCatalogue(client, { limit, walkAll, cursor });
|
|
45
|
+
const meta = buildMeta({
|
|
46
|
+
data,
|
|
47
|
+
source,
|
|
48
|
+
sampled: !walkAll,
|
|
49
|
+
sampleSize: data.products.returned,
|
|
50
|
+
gapsMethod: walkAll ? "exact" : "sample"
|
|
51
|
+
});
|
|
52
|
+
if (isJsonMode()) {
|
|
53
|
+
result({ schema_version: SCHEMA_VERSION, data, meta });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
renderHuman(data, meta);
|
|
57
|
+
}
|
|
58
|
+
async function resolvePublicKey(args) {
|
|
59
|
+
const fromFlag = flagString(args, FLAG_PUBLIC_KEY);
|
|
60
|
+
if (fromFlag) return fromFlag;
|
|
61
|
+
const fromProc = process.env[PUBLIC_KEY_ENV]?.trim();
|
|
62
|
+
if (fromProc) return fromProc;
|
|
63
|
+
for (const name of ENV_FILE_CANDIDATES) {
|
|
64
|
+
const filePath = path.join(process.cwd(), name);
|
|
65
|
+
try {
|
|
66
|
+
const text = await promises.readFile(filePath, "utf8");
|
|
67
|
+
const entries = parseEnvFile(text);
|
|
68
|
+
const hit = entries.find((e) => e.key === PUBLIC_KEY_ENV);
|
|
69
|
+
const value = hit?.value.trim();
|
|
70
|
+
if (value) return value;
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw new CliError(
|
|
75
|
+
CLI_ERROR_CODE.INVALID_INPUT,
|
|
76
|
+
`Set ${PUBLIC_KEY_ENV} in .env or pass --public-key. Get one at desk.cimplify.io/settings/api.`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
function isHostedKey(key) {
|
|
80
|
+
return key.startsWith("cpk_live_") || key.startsWith("cpk_test_");
|
|
81
|
+
}
|
|
82
|
+
function parseLimit(raw) {
|
|
83
|
+
if (!raw) return DEFAULT_LIMIT;
|
|
84
|
+
const n = Number.parseInt(raw, 10);
|
|
85
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
86
|
+
throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `--limit must be a positive integer.`);
|
|
87
|
+
}
|
|
88
|
+
return n;
|
|
89
|
+
}
|
|
90
|
+
async function fetchCatalogue(client, opts) {
|
|
91
|
+
const [productsPage, collectionsList, categoriesList] = await Promise.all([
|
|
92
|
+
fetchProducts(client, opts),
|
|
93
|
+
unwrap(client.catalogue.getCollections(), "list collections"),
|
|
94
|
+
unwrap(client.catalogue.getCategories(), "list categories")
|
|
95
|
+
]);
|
|
96
|
+
const collections = await Promise.all(
|
|
97
|
+
collectionsList.map(async (c) => buildCollectionSummary(client, c))
|
|
98
|
+
);
|
|
99
|
+
const collectionMembers = /* @__PURE__ */ new Set();
|
|
100
|
+
for (const c of collections) {
|
|
101
|
+
for (const pid of c.sample_product_ids) collectionMembers.add(pid);
|
|
102
|
+
}
|
|
103
|
+
const tagFreq = countTags(productsPage.items);
|
|
104
|
+
const distinctTags = tagFreq.size;
|
|
105
|
+
const topTags = [...tagFreq.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, TAG_TOP_N).map(([tag, count]) => ({ tag, count }));
|
|
106
|
+
const sample = productsPage.items.map((p) => productToSampleRow(p));
|
|
107
|
+
const gaps = computeGaps(productsPage.items, collections, categoriesList, collectionMembers);
|
|
108
|
+
return {
|
|
109
|
+
business: await fetchBusiness(client),
|
|
110
|
+
totals: {
|
|
111
|
+
products: productsPage.total ?? productsPage.items.length,
|
|
112
|
+
collections: collectionsList.length,
|
|
113
|
+
categories: categoriesList.length,
|
|
114
|
+
distinct_tags: distinctTags
|
|
115
|
+
},
|
|
116
|
+
gaps,
|
|
117
|
+
products: {
|
|
118
|
+
sample,
|
|
119
|
+
next_cursor: productsPage.nextCursor,
|
|
120
|
+
returned: productsPage.items.length
|
|
121
|
+
},
|
|
122
|
+
collections,
|
|
123
|
+
categories: { tree: buildCategoryTree(categoriesList) },
|
|
124
|
+
tags: { top: topTags }
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function fetchProducts(client, opts) {
|
|
128
|
+
const first = await unwrap(
|
|
129
|
+
client.catalogue.getProducts({ limit: opts.limit, cursor: opts.cursor }),
|
|
130
|
+
"list products"
|
|
131
|
+
);
|
|
132
|
+
if (!opts.walkAll) {
|
|
133
|
+
return {
|
|
134
|
+
items: first.items,
|
|
135
|
+
total: first.total_available ?? null,
|
|
136
|
+
nextCursor: extractCursor(first)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const all = [...first.items];
|
|
140
|
+
let cursor = extractCursor(first);
|
|
141
|
+
while (cursor) {
|
|
142
|
+
const page = await unwrap(
|
|
143
|
+
client.catalogue.getProducts({ limit: opts.limit, cursor }),
|
|
144
|
+
"walk products"
|
|
145
|
+
);
|
|
146
|
+
all.push(...page.items);
|
|
147
|
+
cursor = extractCursor(page);
|
|
148
|
+
}
|
|
149
|
+
return { items: all, total: first.total_available ?? all.length, nextCursor: null };
|
|
150
|
+
}
|
|
151
|
+
function extractCursor(page) {
|
|
152
|
+
return page.pagination?.next_cursor ?? null;
|
|
153
|
+
}
|
|
154
|
+
async function buildCollectionSummary(client, c) {
|
|
155
|
+
const r = await client.catalogue.getCollectionProducts(c.id);
|
|
156
|
+
const products = r.ok ? Array.isArray(r.value) ? r.value : r.value.items ?? [] : [];
|
|
157
|
+
return {
|
|
158
|
+
id: c.id,
|
|
159
|
+
name: c.name,
|
|
160
|
+
slug: c.slug ?? "",
|
|
161
|
+
product_count: products.length,
|
|
162
|
+
sample_product_ids: products.slice(0, COLLECTION_SAMPLE_SIZE).map((p) => p.id)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async function fetchBusiness(client) {
|
|
166
|
+
const maybe = client.business.getBusiness;
|
|
167
|
+
if (typeof maybe !== "function") {
|
|
168
|
+
return { handle: null, name: null, currencies: [], locations: 0 };
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const r = await maybe.call(client.business);
|
|
172
|
+
if (!r?.ok || !r.value) return { handle: null, name: null, currencies: [], locations: 0 };
|
|
173
|
+
return {
|
|
174
|
+
handle: r.value.handle ?? null,
|
|
175
|
+
name: r.value.name ?? null,
|
|
176
|
+
currencies: r.value.currencies ?? [],
|
|
177
|
+
locations: r.value.locations?.length ?? 0
|
|
178
|
+
};
|
|
179
|
+
} catch {
|
|
180
|
+
return { handle: null, name: null, currencies: [], locations: 0 };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function productToSampleRow(p) {
|
|
184
|
+
return {
|
|
185
|
+
id: p.id,
|
|
186
|
+
name: p.name,
|
|
187
|
+
slug: p.slug,
|
|
188
|
+
price: priceShape(p),
|
|
189
|
+
has_image: hasImage(p),
|
|
190
|
+
has_description: !!p.description?.trim(),
|
|
191
|
+
tags: p.tags ?? [],
|
|
192
|
+
collection_ids: [],
|
|
193
|
+
category_ids: p.category_id ? [p.category_id] : [],
|
|
194
|
+
variant_count: 0
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function hasImage(p) {
|
|
198
|
+
if (p.image_url && p.image_url.trim().length > 0) return true;
|
|
199
|
+
if (p.images && p.images.length > 0) return true;
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
function hasPrice(p) {
|
|
203
|
+
const price = p.default_price;
|
|
204
|
+
if (!price) return false;
|
|
205
|
+
const amt = price.amount;
|
|
206
|
+
if (amt == null) return false;
|
|
207
|
+
const n = typeof amt === "number" ? amt : Number.parseFloat(String(amt));
|
|
208
|
+
return Number.isFinite(n) && n > 0;
|
|
209
|
+
}
|
|
210
|
+
function priceShape(p) {
|
|
211
|
+
const price = p.default_price;
|
|
212
|
+
if (!price || price.amount == null) return null;
|
|
213
|
+
return { amount: String(price.amount), currency: price.currency ?? "" };
|
|
214
|
+
}
|
|
215
|
+
function countTags(products) {
|
|
216
|
+
const m = /* @__PURE__ */ new Map();
|
|
217
|
+
for (const p of products) {
|
|
218
|
+
for (const t of p.tags ?? []) {
|
|
219
|
+
m.set(t, (m.get(t) ?? 0) + 1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return m;
|
|
223
|
+
}
|
|
224
|
+
function computeGaps(products, collections, categories, collectionMembers) {
|
|
225
|
+
let no_image = 0;
|
|
226
|
+
let no_description = 0;
|
|
227
|
+
let no_price = 0;
|
|
228
|
+
let no_collection = 0;
|
|
229
|
+
let no_tags = 0;
|
|
230
|
+
for (const p of products) {
|
|
231
|
+
if (!hasImage(p)) no_image++;
|
|
232
|
+
if (!p.description?.trim()) no_description++;
|
|
233
|
+
if (!hasPrice(p)) no_price++;
|
|
234
|
+
if (!collectionMembers.has(p.id)) no_collection++;
|
|
235
|
+
if (!p.tags?.length) no_tags++;
|
|
236
|
+
}
|
|
237
|
+
const empty_collections = collections.filter((c) => c.product_count === 0).map((c) => ({ id: c.id, name: c.name }));
|
|
238
|
+
const productCountsByCategory = /* @__PURE__ */ new Map();
|
|
239
|
+
for (const p of products) {
|
|
240
|
+
if (p.category_id) {
|
|
241
|
+
productCountsByCategory.set(
|
|
242
|
+
p.category_id,
|
|
243
|
+
(productCountsByCategory.get(p.category_id) ?? 0) + 1
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const empty_categories = categories.filter((c) => (productCountsByCategory.get(c.id) ?? 0) === 0).map((c) => ({ id: c.id, name: c.name }));
|
|
248
|
+
return { no_image, no_description, no_price, no_collection, no_tags, empty_collections, empty_categories };
|
|
249
|
+
}
|
|
250
|
+
function buildCategoryTree(categories) {
|
|
251
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
252
|
+
for (const c of categories) {
|
|
253
|
+
const key = c.parent_id ?? null;
|
|
254
|
+
if (!byParent.has(key)) byParent.set(key, []);
|
|
255
|
+
byParent.get(key).push(c);
|
|
256
|
+
}
|
|
257
|
+
const build = (parent) => (byParent.get(parent) ?? []).map((c) => ({
|
|
258
|
+
id: c.id,
|
|
259
|
+
name: c.name,
|
|
260
|
+
slug: c.slug ?? "",
|
|
261
|
+
product_count: 0,
|
|
262
|
+
children: build(c.id)
|
|
263
|
+
}));
|
|
264
|
+
return build(null);
|
|
265
|
+
}
|
|
266
|
+
function buildMeta(input) {
|
|
267
|
+
return {
|
|
268
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
269
|
+
source: input.source,
|
|
270
|
+
sampled: input.sampled,
|
|
271
|
+
sample_size: input.sampleSize,
|
|
272
|
+
gaps_method: input.gapsMethod,
|
|
273
|
+
shape_hints: shapeHints(input.data, input.source).sort(),
|
|
274
|
+
suggestions: suggestions(input.data),
|
|
275
|
+
suggested_commands: suggestedCommands(input.sampled)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function shapeHints(d, source) {
|
|
279
|
+
const hints = [];
|
|
280
|
+
const total = d.totals.products;
|
|
281
|
+
if (total === 0) hints.push("empty_catalogue");
|
|
282
|
+
else if (total < 10) hints.push("tiny_catalogue");
|
|
283
|
+
else if (total < 100) hints.push("small_catalogue");
|
|
284
|
+
else if (total < 1e3) hints.push("medium_catalogue");
|
|
285
|
+
else hints.push("large_catalogue");
|
|
286
|
+
if (d.collections.some((c) => c.product_count > 0)) hints.push("uses_collections");
|
|
287
|
+
if (d.categories.tree.length > 0) hints.push("uses_categories");
|
|
288
|
+
if (d.gaps.empty_collections.length > 0) hints.push("has_empty_collection");
|
|
289
|
+
if (d.gaps.empty_categories.length > 0) hints.push("has_empty_category");
|
|
290
|
+
const sampleSize = d.products.sample.length;
|
|
291
|
+
if (sampleSize > 0 && d.gaps.no_tags / sampleSize > 0.5) hints.push("untagged_majority");
|
|
292
|
+
if (sampleSize > 0 && d.gaps.no_collection / sampleSize > 0.5) hints.push("unmerchandised_majority");
|
|
293
|
+
if (d.gaps.no_image > 0) hints.push("has_image_gaps");
|
|
294
|
+
if (d.gaps.no_description > 0) hints.push("has_description_gaps");
|
|
295
|
+
if (d.gaps.no_price > 0) hints.push("has_price_gaps");
|
|
296
|
+
if (d.business.currencies.length > 1) hints.push("multi_currency");
|
|
297
|
+
if (d.business.locations > 1) hints.push("multi_location");
|
|
298
|
+
if (source === "mock") hints.push("mock_data");
|
|
299
|
+
return hints;
|
|
300
|
+
}
|
|
301
|
+
function suggestions(d) {
|
|
302
|
+
const out = [];
|
|
303
|
+
if (d.totals.products === 0) {
|
|
304
|
+
out.push({ kind: "add_first_product" });
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
if (d.totals.collections === 0) out.push({ kind: "add_first_collection" });
|
|
308
|
+
if (d.gaps.no_image > 0) out.push({ kind: "fix_no_image", count: d.gaps.no_image });
|
|
309
|
+
if (d.gaps.no_description > 0)
|
|
310
|
+
out.push({ kind: "fix_no_description", count: d.gaps.no_description });
|
|
311
|
+
if (d.gaps.no_price > 0) out.push({ kind: "fix_no_price", count: d.gaps.no_price });
|
|
312
|
+
if (d.gaps.no_collection > 0)
|
|
313
|
+
out.push({ kind: "fix_no_collection", count: d.gaps.no_collection });
|
|
314
|
+
if (d.gaps.no_tags > 0) out.push({ kind: "fix_no_tags", count: d.gaps.no_tags });
|
|
315
|
+
for (const c of d.gaps.empty_collections) {
|
|
316
|
+
out.push({ kind: "merchandise_empty_collection", collection_id: c.id, name: c.name });
|
|
317
|
+
}
|
|
318
|
+
for (const c of d.gaps.empty_categories) {
|
|
319
|
+
out.push({ kind: "merchandise_empty_category", category_id: c.id, name: c.name });
|
|
320
|
+
}
|
|
321
|
+
if (d.totals.products >= 1e3) {
|
|
322
|
+
out.push({ kind: "enable_search_ui", product_count: d.totals.products });
|
|
323
|
+
}
|
|
324
|
+
return out;
|
|
325
|
+
}
|
|
326
|
+
function suggestedCommands(sampled) {
|
|
327
|
+
return sampled ? ["cimplify inspect catalogue --all", "cimplify inspect catalogue --json"] : ["cimplify inspect catalogue --json"];
|
|
328
|
+
}
|
|
329
|
+
function renderHuman(d, m) {
|
|
330
|
+
info("");
|
|
331
|
+
info(
|
|
332
|
+
` ${bold(d.business.name ?? "(unnamed business)")}` + (d.business.handle ? dim(` \xB7 @${d.business.handle}`) : "") + (d.business.locations ? dim(` \xB7 ${d.business.locations} location${d.business.locations === 1 ? "" : "s"}`) : "") + (d.business.currencies.length ? dim(` \xB7 ${d.business.currencies.join(", ")}`) : "")
|
|
333
|
+
);
|
|
334
|
+
info("");
|
|
335
|
+
info(` Products ${pad(d.totals.products, 5)} ${gapBadges(d.gaps)}`);
|
|
336
|
+
info(` Collections ${pad(d.totals.collections, 5)} ${emptyBadge(d.gaps.empty_collections, "empty")}`);
|
|
337
|
+
info(` Categories ${pad(d.totals.categories, 5)} ${emptyBadge(d.gaps.empty_categories, "empty")}`);
|
|
338
|
+
info(` Tags ${pad(d.totals.distinct_tags, 5)} ${tagBadge(d.gaps.no_tags)}`);
|
|
339
|
+
info("");
|
|
340
|
+
if (d.tags.top.length > 0) {
|
|
341
|
+
info(` ${dim("Top tags")} ${d.tags.top.map((t) => `${t.tag} (${t.count})`).join(" \xB7 ")}`);
|
|
342
|
+
info("");
|
|
343
|
+
}
|
|
344
|
+
if (d.collections.length > 0) {
|
|
345
|
+
info(` ${dim("Collections")}`);
|
|
346
|
+
for (const c of d.collections.slice(0, 5)) {
|
|
347
|
+
const tag = c.product_count === 0 ? ` ${yellow("\u26A0 empty")}` : "";
|
|
348
|
+
info(` ${green("\u25B8")} ${c.name.padEnd(20)} ${dim(`${c.product_count} items`)}${tag}`);
|
|
349
|
+
}
|
|
350
|
+
if (d.collections.length > 5) info(` ${dim(`\u2026${d.collections.length - 5} more`)}`);
|
|
351
|
+
info("");
|
|
352
|
+
}
|
|
353
|
+
if (d.products.sample.length > 0) {
|
|
354
|
+
info(` ${dim(`Sample products (first ${d.products.sample.length} of ${d.totals.products})`)}`);
|
|
355
|
+
for (const p of d.products.sample.slice(0, 10)) {
|
|
356
|
+
const id = p.id.slice(0, 8).padEnd(10);
|
|
357
|
+
const name = truncate(p.name, 24).padEnd(24);
|
|
358
|
+
const price = p.price ? `${p.price.currency} ${p.price.amount}` : dim("\u2014");
|
|
359
|
+
const img = p.has_image ? green("img \u2713") : red("img \u2717");
|
|
360
|
+
const tags = p.tags.length ? p.tags.slice(0, 2).join(", ") : dim("\u2014");
|
|
361
|
+
info(` ${dim(id)}${name} ${price.padEnd(12)} ${img} ${tags}`);
|
|
362
|
+
}
|
|
363
|
+
info("");
|
|
364
|
+
}
|
|
365
|
+
if (m.sampled) {
|
|
366
|
+
info(` ${dim(`Gaps computed from first ${m.sample_size} products. Add --all for exact counts.`)}`);
|
|
367
|
+
} else {
|
|
368
|
+
info(` ${dim("Gaps are exact (walked the full catalogue).")}`);
|
|
369
|
+
}
|
|
370
|
+
info("");
|
|
371
|
+
if (m.suggested_commands.length > 0) {
|
|
372
|
+
info(` ${dim("Try")}`);
|
|
373
|
+
for (const c of m.suggested_commands) info(` ${c}`);
|
|
374
|
+
info("");
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function gapBadges(g, _kind) {
|
|
378
|
+
const bits = [];
|
|
379
|
+
if (g.no_image > 0) bits.push(`${g.no_image} no image`);
|
|
380
|
+
if (g.no_description > 0) bits.push(`${g.no_description} no description`);
|
|
381
|
+
if (g.no_collection > 0) bits.push(`${g.no_collection} not in any collection`);
|
|
382
|
+
if (bits.length === 0) return dim("no gaps");
|
|
383
|
+
return yellow(`\u26A0 ${bits.join(" \xB7 ")}`);
|
|
384
|
+
}
|
|
385
|
+
function emptyBadge(items, label) {
|
|
386
|
+
if (items.length === 0) return dim("no gaps");
|
|
387
|
+
const names = items.slice(0, 3).map((i) => i.name).join(", ");
|
|
388
|
+
const more = items.length > 3 ? `, \u2026${items.length - 3} more` : "";
|
|
389
|
+
return yellow(`\u26A0 ${items.length} ${label} (${names}${more})`);
|
|
390
|
+
}
|
|
391
|
+
function tagBadge(untagged) {
|
|
392
|
+
if (untagged === 0) return dim("all tagged");
|
|
393
|
+
return yellow(`\u26A0 ${untagged} products untagged`);
|
|
394
|
+
}
|
|
395
|
+
function pad(n, width) {
|
|
396
|
+
return String(n).padStart(width);
|
|
397
|
+
}
|
|
398
|
+
function truncate(s, width) {
|
|
399
|
+
if (s.length <= width) return s;
|
|
400
|
+
return s.slice(0, width - 1) + "\u2026";
|
|
401
|
+
}
|
|
402
|
+
async function unwrap(p, label) {
|
|
403
|
+
const r = await p;
|
|
404
|
+
if (!r.ok || r.value == null) {
|
|
405
|
+
const msg = r.error?.message ?? `failed to ${label}`;
|
|
406
|
+
throw new CliError(CLI_ERROR_CODE.NETWORK_ERROR, msg);
|
|
407
|
+
}
|
|
408
|
+
return r.value;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export { computeGaps, run as default, isHostedKey, parseLimit, shapeHints, suggestions };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { run as default, extractMockSeed, gatherIntrospection, renderIntrospection } from './chunk-
|
|
2
|
+
export { run as default, extractMockSeed, gatherIntrospection, renderIntrospection } from './chunk-A2L5IV57.mjs';
|
|
3
3
|
import './chunk-K5464A3L.mjs';
|
|
4
4
|
import './chunk-DBZ3UOQ2.mjs';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-E7P6GL73.mjs';
|
|
6
6
|
import './chunk-C4M3DXKC.mjs';
|
|
7
7
|
import './chunk-UBAI443T.mjs';
|
|
8
8
|
import './chunk-E2T2SBP5.mjs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { REGISTRY_INDEX } from './chunk-
|
|
2
|
+
import { REGISTRY_INDEX } from './chunk-VOQJ7GYE.mjs';
|
|
3
3
|
import { parseArgs, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
4
4
|
import { CliError, CLI_ERROR_CODE, info, bold, dim, green, result } from './chunk-E2T2SBP5.mjs';
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { run as default, fetchCloneToken } from './chunk-
|
|
2
|
+
export { run as default, fetchCloneToken, fetchRepoRecord } from './chunk-CLNS2NBR.mjs';
|
|
3
3
|
import './chunk-ITAFAORS.mjs';
|
|
4
4
|
import './chunk-MXYUAJEW.mjs';
|
|
5
5
|
import './chunk-C4M3DXKC.mjs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { package_default } from './chunk-
|
|
2
|
+
import { package_default } from './chunk-E7P6GL73.mjs';
|
|
3
3
|
import { promptYesNo } from './chunk-ITAFAORS.mjs';
|
|
4
4
|
import { parseArgs, flagBool, flagString } from './chunk-C4M3DXKC.mjs';
|
|
5
5
|
import { success, bold, info, dim, result, failure, CliError, CLI_ERROR_CODE, step, isJsonMode } from './chunk-E2T2SBP5.mjs';
|