@supercycle/cli 0.1.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 +107 -0
- package/dist/cli.js +9 -0
- package/dist/commands/comments/delete.js +48 -0
- package/dist/commands/comments/index.js +3 -0
- package/dist/commands/conditions/index.js +3 -0
- package/dist/commands/conditions/list.js +20 -0
- package/dist/commands/custom-fields/definition.js +58 -0
- package/dist/commands/custom-fields/definitions.js +25 -0
- package/dist/commands/custom-fields/delete.js +45 -0
- package/dist/commands/custom-fields/index.js +3 -0
- package/dist/commands/custom-fields/set.js +43 -0
- package/dist/commands/custom-fields/update.js +19 -0
- package/dist/commands/cycles/comment.js +29 -0
- package/dist/commands/cycles/fulfil.js +24 -0
- package/dist/commands/cycles/get.js +50 -0
- package/dist/commands/cycles/index.js +3 -0
- package/dist/commands/cycles/list.js +124 -0
- package/dist/commands/cycles/overdue.js +25 -0
- package/dist/commands/cycles/pack.js +21 -0
- package/dist/commands/cycles/reassign.js +28 -0
- package/dist/commands/cycles/receive.js +23 -0
- package/dist/commands/cycles/reschedule.js +74 -0
- package/dist/commands/cycles/return.js +52 -0
- package/dist/commands/cycles/tag.js +45 -0
- package/dist/commands/cycles/to-fulfill.js +9 -0
- package/dist/commands/cycles/to-receive.js +9 -0
- package/dist/commands/cycles/today.js +18 -0
- package/dist/commands/items/availability.js +75 -0
- package/dist/commands/items/comment.js +29 -0
- package/dist/commands/items/create.js +57 -0
- package/dist/commands/items/get.js +50 -0
- package/dist/commands/items/index.js +3 -0
- package/dist/commands/items/list.js +25 -0
- package/dist/commands/items/update.js +60 -0
- package/dist/commands/locations/index.js +3 -0
- package/dist/commands/locations/list.js +19 -0
- package/dist/commands/login.js +76 -0
- package/dist/commands/products/import.js +65 -0
- package/dist/commands/products/index.js +3 -0
- package/dist/commands/products/list.js +19 -0
- package/dist/commands/returns/comment.js +29 -0
- package/dist/commands/returns/get.js +50 -0
- package/dist/commands/returns/index.js +3 -0
- package/dist/commands/returns/list.js +27 -0
- package/dist/commands/returns/update.js +68 -0
- package/dist/components/CommentResultView.js +40 -0
- package/dist/components/CustomFieldResultView.js +46 -0
- package/dist/components/CycleActionView.js +36 -0
- package/dist/components/CycleDetail.js +60 -0
- package/dist/components/CycleTable.js +68 -0
- package/dist/components/CyclesListView.js +30 -0
- package/dist/components/ErrorMessage.js +13 -0
- package/dist/components/ExitError.js +13 -0
- package/dist/components/ItemActionView.js +36 -0
- package/dist/components/ItemDetail.js +48 -0
- package/dist/components/ItemTable.js +58 -0
- package/dist/components/ItemsListView.js +30 -0
- package/dist/components/ItemsResultView.js +37 -0
- package/dist/components/JsonOutput.js +12 -0
- package/dist/components/Loading.js +11 -0
- package/dist/components/ProductTable.js +49 -0
- package/dist/components/ProductsListView.js +30 -0
- package/dist/components/ReferenceListView.js +49 -0
- package/dist/components/ReturnActionView.js +36 -0
- package/dist/components/ReturnDetail.js +55 -0
- package/dist/components/ReturnResultView.js +49 -0
- package/dist/components/ReturnTable.js +58 -0
- package/dist/components/ReturnsListView.js +30 -0
- package/dist/components/Status.js +33 -0
- package/dist/hooks/useAsync.js +32 -0
- package/dist/lib/client.js +302 -0
- package/dist/lib/config.js +38 -0
- package/dist/lib/format.js +49 -0
- package/dist/lib/listViewOptions.js +39 -0
- package/dist/lib/resolveClient.js +11 -0
- package/dist/lib/types.js +3 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Supercycle CLI
|
|
2
|
+
|
|
3
|
+
A command-line interface for the [Supercycle Admin API](https://docs.supercycle.com/api-reference/admin/introduction), built with Node and [Ink](https://github.com/vadimdemedes/ink).
|
|
4
|
+
|
|
5
|
+
> In the Supercycle UI a **cycle** is modelled as a **rental** in the API. This CLI uses the "cycle" terminology.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Node.js ≥ 18
|
|
10
|
+
- A Supercycle Admin API key (Superest or custom plans — created in Supercycle → Settings → API)
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pnpm install
|
|
16
|
+
pnpm build
|
|
17
|
+
pnpm link --global # optional: makes `supercycle` available globally
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
During development, run commands with `node dist/cli.js …` (rebuild with `pnpm build`, or `pnpm dev` to watch).
|
|
21
|
+
|
|
22
|
+
Code quality:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pnpm fmt # format with oxfmt
|
|
26
|
+
pnpm fmt:check # verify formatting (CI)
|
|
27
|
+
pnpm lint # lint with oxlint
|
|
28
|
+
pnpm lint:fix # lint and auto-fix
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Authentication
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
supercycle login # prompts for your API key, validates, and stores it
|
|
35
|
+
supercycle login --key sk_… # non-interactive
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The key is stored in `~/.config/supercycle/config.json` (mode `600`). You can
|
|
39
|
+
override it with the `SUPERCYCLE_API_KEY` environment variable.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
List cycles, with optional filters:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
supercycle cycles list
|
|
47
|
+
supercycle cycles list --fulfillment overdue
|
|
48
|
+
supercycle cycles list --search "jane" --limit 20
|
|
49
|
+
supercycle cycles list --rental-start-from 2024-01-01 --rental-start-to 2024-01-31
|
|
50
|
+
supercycle cycles list --updated-since 2024-06-01 # incremental sync
|
|
51
|
+
supercycle cycles list --page <cursor> # next page (cursor from previous output)
|
|
52
|
+
supercycle cycles list --all # auto-follow every page
|
|
53
|
+
supercycle cycles list --json | jq '.data | length'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Retrieve a single cycle:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
supercycle cycles get 12345
|
|
60
|
+
supercycle cycles get 12345 --include item,timelineEvents
|
|
61
|
+
supercycle cycles get 12345 --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Read shortcuts
|
|
65
|
+
|
|
66
|
+
Pre-filtered views (each accepts `--search`, `--limit`, `--page`, `--all`, `--json`):
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
supercycle cycles to-fulfill # awaiting dispatch (unfulfilled)
|
|
70
|
+
supercycle cycles to-receive # awaiting return (receival due)
|
|
71
|
+
supercycle cycles overdue # overdue returns (--type fulfillment for dispatches)
|
|
72
|
+
supercycle cycles today # scheduled to be received back today
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Managing cycles (writes)
|
|
76
|
+
|
|
77
|
+
> These update Supercycle only — they do **not** change the linked Shopify order/fulfilment.
|
|
78
|
+
> Most print the updated cycle; add `--json` for raw output.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
supercycle cycles fulfil 12345 # mark dispatched (now); --at <iso> to backdate
|
|
82
|
+
supercycle cycles receive 12345 # mark received back; --at <iso>
|
|
83
|
+
supercycle cycles pack 12345 --status packed # pending|printed|packed
|
|
84
|
+
supercycle cycles reschedule 12345 --fulfill-at 2024-07-01T09:00:00Z --receive-at 2024-07-10T17:00:00Z
|
|
85
|
+
supercycle cycles reassign 12345 --item-id 678 --reallocate
|
|
86
|
+
supercycle cycles tag 12345 --add "vip,priority" --remove "draft"
|
|
87
|
+
supercycle cycles return 12345 --status awaiting --method collection --collection-date 2024-07-05
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### List filters
|
|
91
|
+
|
|
92
|
+
All `GET /rentals` filters are exposed:
|
|
93
|
+
|
|
94
|
+
- `--search` — title / customer text
|
|
95
|
+
- `--fulfillment`, `--receival` — `due|scheduled|overdue|complete`
|
|
96
|
+
- `--unfulfilled`, `--exclude-cancelled` — booleans
|
|
97
|
+
- `--customer-id`, `--item-id` — numeric
|
|
98
|
+
- `--shopify-order-id`, `--shopify-variant-id`, `--shopify-line-item-id`
|
|
99
|
+
- `--return-order-status` — comma-separated (`requested,expected,received,in_progress,completed,cancelled`)
|
|
100
|
+
- Date ranges (`--*-from` → `gte`, `--*-to` → `lte`, inclusive, ISO dates):
|
|
101
|
+
`--rental-start-from/-to`, `--created-from/-to`, `--receive-from/-to`,
|
|
102
|
+
and `--updated-since` (gte, for incremental sync) / `--updated-to`
|
|
103
|
+
- `--include` — relations to include (e.g. `item`)
|
|
104
|
+
- `--limit` (1–100, default 50), `--page <cursor>`, `--json`
|
|
105
|
+
|
|
106
|
+
Pagination is keyset-based: when more results exist, the output prints a
|
|
107
|
+
`--page <cursor>` hint to fetch the next page.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Text } from "ink";
|
|
2
|
+
import { argument, option } from "pastel";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import zod from "zod";
|
|
5
|
+
import ErrorMessage from "../../components/ErrorMessage.js";
|
|
6
|
+
import ExitError from "../../components/ExitError.js";
|
|
7
|
+
import JsonOutput from "../../components/JsonOutput.js";
|
|
8
|
+
import Loading from "../../components/Loading.js";
|
|
9
|
+
import { useAsync } from "../../hooks/useAsync.js";
|
|
10
|
+
import { resolveClient, NO_TOKEN_HINT } from "../../lib/resolveClient.js";
|
|
11
|
+
export const description = "Delete a timeline comment by ID";
|
|
12
|
+
export const args = zod.tuple([
|
|
13
|
+
zod.string().describe(argument({
|
|
14
|
+
name: "id",
|
|
15
|
+
description: "Numeric ID of the timeline comment (from a resource's timeline events)",
|
|
16
|
+
})),
|
|
17
|
+
]);
|
|
18
|
+
export const options = zod.object({
|
|
19
|
+
json: zod
|
|
20
|
+
.boolean()
|
|
21
|
+
.default(false)
|
|
22
|
+
.describe(option({ description: "Output raw JSON" })),
|
|
23
|
+
});
|
|
24
|
+
export default function Delete({ args, options }) {
|
|
25
|
+
const [id] = args;
|
|
26
|
+
const resolved = resolveClient();
|
|
27
|
+
const state = useAsync(async () => {
|
|
28
|
+
if (!resolved.client)
|
|
29
|
+
throw new Error(resolved.error);
|
|
30
|
+
await resolved.client.deleteTimelineComment(id);
|
|
31
|
+
return { id, deleted: true };
|
|
32
|
+
}, []);
|
|
33
|
+
if (state.loading) {
|
|
34
|
+
return options.json ? null : React.createElement(Loading, { label: `Deleting comment ${id}…` });
|
|
35
|
+
}
|
|
36
|
+
if (state.error) {
|
|
37
|
+
if (options.json)
|
|
38
|
+
return React.createElement(ExitError, { message: state.error.message });
|
|
39
|
+
return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
|
|
40
|
+
}
|
|
41
|
+
if (options.json) {
|
|
42
|
+
return React.createElement(JsonOutput, { data: state.data });
|
|
43
|
+
}
|
|
44
|
+
return React.createElement(Text, { color: "green" },
|
|
45
|
+
"\u2714 Comment ",
|
|
46
|
+
id,
|
|
47
|
+
" deleted");
|
|
48
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { option } from "pastel";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import zod from "zod";
|
|
4
|
+
import ReferenceListView from "../../components/ReferenceListView.js";
|
|
5
|
+
import { dash, titleCase } from "../../lib/format.js";
|
|
6
|
+
export const description = "List all conditions";
|
|
7
|
+
const COLUMNS = [
|
|
8
|
+
{ label: "ID", width: 8, value: (c) => dash(c.id) },
|
|
9
|
+
{ label: "Condition", width: 28, value: (c) => dash(c.title) },
|
|
10
|
+
{ label: "Severity", value: (c) => titleCase(c.severityKey) },
|
|
11
|
+
];
|
|
12
|
+
export const options = zod.object({
|
|
13
|
+
json: zod
|
|
14
|
+
.boolean()
|
|
15
|
+
.default(false)
|
|
16
|
+
.describe(option({ description: "Output raw JSON" })),
|
|
17
|
+
});
|
|
18
|
+
export default function List({ options }) {
|
|
19
|
+
return (React.createElement(ReferenceListView, { fetch: (client) => client.listConditions(), columns: COLUMNS, rowKey: (c) => c.id, loadingLabel: "Fetching conditions\u2026", emptyLabel: "No conditions found.", noun: "condition", json: options.json }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { argument, option } from "pastel";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import zod from "zod";
|
|
5
|
+
import ErrorMessage from "../../components/ErrorMessage.js";
|
|
6
|
+
import ExitError from "../../components/ExitError.js";
|
|
7
|
+
import JsonOutput from "../../components/JsonOutput.js";
|
|
8
|
+
import Loading from "../../components/Loading.js";
|
|
9
|
+
import { useAsync } from "../../hooks/useAsync.js";
|
|
10
|
+
import { dash, titleCase } from "../../lib/format.js";
|
|
11
|
+
import { resolveClient, NO_TOKEN_HINT } from "../../lib/resolveClient.js";
|
|
12
|
+
export const description = "Retrieve a single custom field definition by ID";
|
|
13
|
+
export const args = zod.tuple([
|
|
14
|
+
zod.string().describe(argument({
|
|
15
|
+
name: "id",
|
|
16
|
+
description: "Numeric ID of the custom field definition",
|
|
17
|
+
})),
|
|
18
|
+
]);
|
|
19
|
+
export const options = zod.object({
|
|
20
|
+
json: zod
|
|
21
|
+
.boolean()
|
|
22
|
+
.default(false)
|
|
23
|
+
.describe(option({ description: "Output raw JSON" })),
|
|
24
|
+
});
|
|
25
|
+
function Field({ label, children }) {
|
|
26
|
+
return (React.createElement(Box, null,
|
|
27
|
+
React.createElement(Box, { width: 18 },
|
|
28
|
+
React.createElement(Text, { dimColor: true }, label)),
|
|
29
|
+
React.createElement(Text, null, children)));
|
|
30
|
+
}
|
|
31
|
+
export default function Definition({ args, options }) {
|
|
32
|
+
const [id] = args;
|
|
33
|
+
const resolved = resolveClient();
|
|
34
|
+
const state = useAsync(async () => {
|
|
35
|
+
if (!resolved.client)
|
|
36
|
+
throw new Error(resolved.error);
|
|
37
|
+
return resolved.client.getCustomFieldDefinition(id);
|
|
38
|
+
}, []);
|
|
39
|
+
if (state.loading) {
|
|
40
|
+
return options.json ? null : React.createElement(Loading, { label: `Fetching definition ${id}…` });
|
|
41
|
+
}
|
|
42
|
+
if (state.error) {
|
|
43
|
+
if (options.json)
|
|
44
|
+
return React.createElement(ExitError, { message: state.error.message });
|
|
45
|
+
return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
|
|
46
|
+
}
|
|
47
|
+
if (options.json) {
|
|
48
|
+
return React.createElement(JsonOutput, { data: state.data });
|
|
49
|
+
}
|
|
50
|
+
const def = state.data;
|
|
51
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
52
|
+
React.createElement(Text, { bold: true, underline: true }, def.name),
|
|
53
|
+
React.createElement(Field, { label: "ID" }, String(def.id)),
|
|
54
|
+
React.createElement(Field, { label: "Key" }, dash(def.key)),
|
|
55
|
+
React.createElement(Field, { label: "Owner type" }, titleCase(def.ownerType)),
|
|
56
|
+
React.createElement(Field, { label: "Value type" }, titleCase(def.type)),
|
|
57
|
+
React.createElement(Field, { label: "Groups fulfil." }, def.groupFulfillments ? "yes" : "no")));
|
|
58
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { option } from "pastel";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import zod from "zod";
|
|
4
|
+
import ReferenceListView from "../../components/ReferenceListView.js";
|
|
5
|
+
import { dash, titleCase } from "../../lib/format.js";
|
|
6
|
+
export const description = "List custom field definitions for an owner type";
|
|
7
|
+
const COLUMNS = [
|
|
8
|
+
{ label: "ID", width: 8, value: (d) => dash(d.id) },
|
|
9
|
+
{ label: "Key", width: 24, value: (d) => dash(d.key) },
|
|
10
|
+
{ label: "Name", width: 24, value: (d) => dash(d.name) },
|
|
11
|
+
{ label: "Type", width: 22, value: (d) => titleCase(d.type) },
|
|
12
|
+
{ label: "Group", value: (d) => (d.groupFulfillments ? "yes" : "no") },
|
|
13
|
+
];
|
|
14
|
+
export const options = zod.object({
|
|
15
|
+
ownerType: zod
|
|
16
|
+
.enum(["item", "rental"])
|
|
17
|
+
.describe(option({ description: "Owner type: item|rental (required)" })),
|
|
18
|
+
json: zod
|
|
19
|
+
.boolean()
|
|
20
|
+
.default(false)
|
|
21
|
+
.describe(option({ description: "Output raw JSON" })),
|
|
22
|
+
});
|
|
23
|
+
export default function Definitions({ options }) {
|
|
24
|
+
return (React.createElement(ReferenceListView, { fetch: (client) => client.listCustomFieldDefinitions({ ownerType: options.ownerType }), columns: COLUMNS, rowKey: (d) => d.id, loadingLabel: `Fetching ${options.ownerType} custom field definitions…`, emptyLabel: "No custom field definitions found.", noun: "definition", json: options.json }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Text } from "ink";
|
|
2
|
+
import { argument, option } from "pastel";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import zod from "zod";
|
|
5
|
+
import ErrorMessage from "../../components/ErrorMessage.js";
|
|
6
|
+
import ExitError from "../../components/ExitError.js";
|
|
7
|
+
import JsonOutput from "../../components/JsonOutput.js";
|
|
8
|
+
import Loading from "../../components/Loading.js";
|
|
9
|
+
import { useAsync } from "../../hooks/useAsync.js";
|
|
10
|
+
import { resolveClient, NO_TOKEN_HINT } from "../../lib/resolveClient.js";
|
|
11
|
+
export const description = "Delete a custom field by ID";
|
|
12
|
+
export const args = zod.tuple([
|
|
13
|
+
zod.string().describe(argument({ name: "id", description: "Numeric custom field ID" })),
|
|
14
|
+
]);
|
|
15
|
+
export const options = zod.object({
|
|
16
|
+
json: zod
|
|
17
|
+
.boolean()
|
|
18
|
+
.default(false)
|
|
19
|
+
.describe(option({ description: "Output raw JSON" })),
|
|
20
|
+
});
|
|
21
|
+
export default function Delete({ args, options }) {
|
|
22
|
+
const [id] = args;
|
|
23
|
+
const resolved = resolveClient();
|
|
24
|
+
const state = useAsync(async () => {
|
|
25
|
+
if (!resolved.client)
|
|
26
|
+
throw new Error(resolved.error);
|
|
27
|
+
await resolved.client.deleteCustomField(id);
|
|
28
|
+
return { id, deleted: true };
|
|
29
|
+
}, []);
|
|
30
|
+
if (state.loading) {
|
|
31
|
+
return options.json ? null : React.createElement(Loading, { label: `Deleting custom field ${id}…` });
|
|
32
|
+
}
|
|
33
|
+
if (state.error) {
|
|
34
|
+
if (options.json)
|
|
35
|
+
return React.createElement(ExitError, { message: state.error.message });
|
|
36
|
+
return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
|
|
37
|
+
}
|
|
38
|
+
if (options.json) {
|
|
39
|
+
return React.createElement(JsonOutput, { data: state.data });
|
|
40
|
+
}
|
|
41
|
+
return React.createElement(Text, { color: "green" },
|
|
42
|
+
"\u2714 Custom field ",
|
|
43
|
+
id,
|
|
44
|
+
" deleted");
|
|
45
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { option } from "pastel";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import zod from "zod";
|
|
4
|
+
import CustomFieldResultView from "../../components/CustomFieldResultView.js";
|
|
5
|
+
import ErrorMessage from "../../components/ErrorMessage.js";
|
|
6
|
+
export const description = "Set a custom field value on an item or rental";
|
|
7
|
+
export const options = zod.object({
|
|
8
|
+
ownerId: zod
|
|
9
|
+
.number()
|
|
10
|
+
.describe(option({ description: "ID of the item or rental to attach the field to (required)" })),
|
|
11
|
+
value: zod.string().describe(option({ description: "Value to store (required)" })),
|
|
12
|
+
definitionId: zod
|
|
13
|
+
.number()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe(option({ description: "Custom field definition ID (or use --key + --owner-type)" })),
|
|
16
|
+
key: zod
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe(option({ description: "Definition key (requires --owner-type)" })),
|
|
20
|
+
ownerType: zod
|
|
21
|
+
.enum(["item", "rental"])
|
|
22
|
+
.optional()
|
|
23
|
+
.describe(option({ description: "Owner type: item|rental (requires --key)" })),
|
|
24
|
+
json: zod
|
|
25
|
+
.boolean()
|
|
26
|
+
.default(false)
|
|
27
|
+
.describe(option({ description: "Output raw JSON" })),
|
|
28
|
+
});
|
|
29
|
+
export default function Set({ options }) {
|
|
30
|
+
const hasDefinition = options.definitionId != null;
|
|
31
|
+
const hasKeyPair = Boolean(options.key && options.ownerType);
|
|
32
|
+
if (!hasDefinition && !hasKeyPair) {
|
|
33
|
+
return (React.createElement(ErrorMessage, { message: "Specify the definition with --definition-id, or with --key and --owner-type." }));
|
|
34
|
+
}
|
|
35
|
+
const body = {
|
|
36
|
+
ownerId: options.ownerId,
|
|
37
|
+
value: options.value,
|
|
38
|
+
definitionId: options.definitionId,
|
|
39
|
+
key: hasDefinition ? undefined : options.key,
|
|
40
|
+
ownerType: hasDefinition ? undefined : options.ownerType,
|
|
41
|
+
};
|
|
42
|
+
return (React.createElement(CustomFieldResultView, { loadingLabel: `Setting custom field on ${options.ownerType ?? "owner"} ${options.ownerId}…`, action: (client) => client.createCustomField(body), successLabel: (f) => `Custom field "${f.key}" set`, json: options.json }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { argument, option } from "pastel";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import zod from "zod";
|
|
4
|
+
import CustomFieldResultView from "../../components/CustomFieldResultView.js";
|
|
5
|
+
export const description = "Update an existing custom field's value";
|
|
6
|
+
export const args = zod.tuple([
|
|
7
|
+
zod.string().describe(argument({ name: "id", description: "Numeric custom field ID" })),
|
|
8
|
+
]);
|
|
9
|
+
export const options = zod.object({
|
|
10
|
+
value: zod.string().describe(option({ description: "New value to store (required)" })),
|
|
11
|
+
json: zod
|
|
12
|
+
.boolean()
|
|
13
|
+
.default(false)
|
|
14
|
+
.describe(option({ description: "Output raw JSON" })),
|
|
15
|
+
});
|
|
16
|
+
export default function Update({ args, options }) {
|
|
17
|
+
const [id] = args;
|
|
18
|
+
return (React.createElement(CustomFieldResultView, { loadingLabel: `Updating custom field ${id}…`, action: (client) => client.updateCustomField(id, options.value), successLabel: (f) => `Custom field "${f.key}" updated`, json: options.json }));
|
|
19
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { argument, option } from "pastel";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import zod from "zod";
|
|
4
|
+
import CommentResultView from "../../components/CommentResultView.js";
|
|
5
|
+
import ErrorMessage from "../../components/ErrorMessage.js";
|
|
6
|
+
export const description = "Add a comment to a cycle's timeline";
|
|
7
|
+
export const args = zod.tuple([
|
|
8
|
+
zod.string().describe(argument({ name: "id", description: "Numeric cycle ID" })),
|
|
9
|
+
zod.string().describe(argument({ name: "message", description: "Comment text" })),
|
|
10
|
+
]);
|
|
11
|
+
export const options = zod.object({
|
|
12
|
+
json: zod
|
|
13
|
+
.boolean()
|
|
14
|
+
.default(false)
|
|
15
|
+
.describe(option({ description: "Output raw JSON" })),
|
|
16
|
+
});
|
|
17
|
+
export default function Comment({ args, options }) {
|
|
18
|
+
const [id, message] = args;
|
|
19
|
+
if (!message.trim()) {
|
|
20
|
+
return React.createElement(ErrorMessage, { message: "Comment message cannot be empty." });
|
|
21
|
+
}
|
|
22
|
+
return (React.createElement(CommentResultView, { loadingLabel: `Adding comment to cycle ${id}…`, action: (client) => client.createTimelineComment({
|
|
23
|
+
timelineEvent: {
|
|
24
|
+
eventableId: Number(id),
|
|
25
|
+
eventableType: "Rental",
|
|
26
|
+
message,
|
|
27
|
+
},
|
|
28
|
+
}), json: options.json }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { argument, option } from "pastel";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import zod from "zod";
|
|
4
|
+
import CycleActionView from "../../components/CycleActionView.js";
|
|
5
|
+
export const description = "Mark a cycle as fulfilled (dispatched)";
|
|
6
|
+
export const args = zod.tuple([
|
|
7
|
+
zod.string().describe(argument({ name: "id", description: "Numeric cycle ID" })),
|
|
8
|
+
]);
|
|
9
|
+
export const options = zod.object({
|
|
10
|
+
at: zod
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe(option({ description: "Dispatch time (ISO date-time; defaults to now)" })),
|
|
14
|
+
json: zod
|
|
15
|
+
.boolean()
|
|
16
|
+
.default(false)
|
|
17
|
+
.describe(option({ description: "Output raw JSON" })),
|
|
18
|
+
});
|
|
19
|
+
// Note: this updates Supercycle only — it does not fulfil the Shopify order.
|
|
20
|
+
export default function Fulfil({ args, options }) {
|
|
21
|
+
const [id] = args;
|
|
22
|
+
const fulfilledAt = options.at ?? new Date().toISOString();
|
|
23
|
+
return (React.createElement(CycleActionView, { loadingLabel: `Fulfilling cycle ${id}…`, action: (client) => client.updateCycle(id, { fulfilledAt }), successLabel: (c) => `Cycle ${c.id} marked as fulfilled`, json: options.json }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { argument, option } from "pastel";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import zod from "zod";
|
|
4
|
+
import CycleDetail from "../../components/CycleDetail.js";
|
|
5
|
+
import ErrorMessage from "../../components/ErrorMessage.js";
|
|
6
|
+
import ExitError from "../../components/ExitError.js";
|
|
7
|
+
import JsonOutput from "../../components/JsonOutput.js";
|
|
8
|
+
import Loading from "../../components/Loading.js";
|
|
9
|
+
import { useAsync } from "../../hooks/useAsync.js";
|
|
10
|
+
import { resolveClient, NO_TOKEN_HINT } from "../../lib/resolveClient.js";
|
|
11
|
+
export const description = "Retrieve a single cycle by ID";
|
|
12
|
+
export const args = zod.tuple([
|
|
13
|
+
zod.string().describe(argument({
|
|
14
|
+
name: "id",
|
|
15
|
+
description: "Numeric ID of the cycle",
|
|
16
|
+
})),
|
|
17
|
+
]);
|
|
18
|
+
export const options = zod.object({
|
|
19
|
+
include: zod
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe(option({
|
|
23
|
+
description: "Relations to include, e.g. item,timelineEvents",
|
|
24
|
+
})),
|
|
25
|
+
json: zod
|
|
26
|
+
.boolean()
|
|
27
|
+
.default(false)
|
|
28
|
+
.describe(option({ description: "Output raw JSON" })),
|
|
29
|
+
});
|
|
30
|
+
export default function Get({ args, options }) {
|
|
31
|
+
const [id] = args;
|
|
32
|
+
const resolved = resolveClient();
|
|
33
|
+
const state = useAsync(async () => {
|
|
34
|
+
if (!resolved.client)
|
|
35
|
+
throw new Error(resolved.error);
|
|
36
|
+
return resolved.client.getCycle(id, { include: options.include });
|
|
37
|
+
}, []);
|
|
38
|
+
if (state.loading) {
|
|
39
|
+
return options.json ? null : React.createElement(Loading, { label: `Fetching cycle ${id}…` });
|
|
40
|
+
}
|
|
41
|
+
if (state.error) {
|
|
42
|
+
if (options.json)
|
|
43
|
+
return React.createElement(ExitError, { message: state.error.message });
|
|
44
|
+
return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
|
|
45
|
+
}
|
|
46
|
+
if (options.json) {
|
|
47
|
+
return React.createElement(JsonOutput, { data: state.data });
|
|
48
|
+
}
|
|
49
|
+
return React.createElement(CycleDetail, { cycle: state.data });
|
|
50
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { option } from "pastel";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import zod from "zod";
|
|
4
|
+
import CyclesListView from "../../components/CyclesListView.js";
|
|
5
|
+
import { listViewShape, viewFilters } from "../../lib/listViewOptions.js";
|
|
6
|
+
export const description = "List cycles, optionally filtered";
|
|
7
|
+
const progress = zod.enum(["due", "scheduled", "overdue", "complete"]);
|
|
8
|
+
export const options = zod.object({
|
|
9
|
+
...listViewShape(),
|
|
10
|
+
fulfillment: progress
|
|
11
|
+
.optional()
|
|
12
|
+
.describe(option({ description: "Fulfillment status: due|scheduled|overdue|complete" })),
|
|
13
|
+
receival: progress
|
|
14
|
+
.optional()
|
|
15
|
+
.describe(option({ description: "Receival status: due|scheduled|overdue|complete" })),
|
|
16
|
+
unfulfilled: zod
|
|
17
|
+
.boolean()
|
|
18
|
+
.default(false)
|
|
19
|
+
.describe(option({ description: "Only show unfulfilled cycles" })),
|
|
20
|
+
excludeCancelled: zod
|
|
21
|
+
.boolean()
|
|
22
|
+
.default(false)
|
|
23
|
+
.describe(option({ description: "Omit cancelled cycles" })),
|
|
24
|
+
customerId: zod
|
|
25
|
+
.number()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe(option({ description: "Filter by customer ID" })),
|
|
28
|
+
itemId: zod
|
|
29
|
+
.number()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe(option({ description: "Filter by item ID" })),
|
|
32
|
+
shopifyOrderId: zod
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe(option({ description: "Filter by Shopify order ID" })),
|
|
36
|
+
shopifyVariantId: zod
|
|
37
|
+
.number()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe(option({ description: "Filter by Shopify variant ID" })),
|
|
40
|
+
shopifyLineItemId: zod
|
|
41
|
+
.number()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe(option({ description: "Filter by Shopify line item ID" })),
|
|
44
|
+
returnOrderStatus: zod
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe(option({
|
|
48
|
+
description: "Return order status (comma-separated): requested,expected,received,in_progress,completed,cancelled",
|
|
49
|
+
})),
|
|
50
|
+
rentalStartFrom: zod
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe(option({ description: "Rental start on/after (ISO date)" })),
|
|
54
|
+
rentalStartTo: zod
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe(option({ description: "Rental start on/before (ISO date)" })),
|
|
58
|
+
createdFrom: zod
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe(option({ description: "Created on/after (ISO date)" })),
|
|
62
|
+
createdTo: zod
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe(option({ description: "Created on/before (ISO date)" })),
|
|
66
|
+
updatedSince: zod
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe(option({ description: "Updated on/after (ISO date), for sync" })),
|
|
70
|
+
updatedTo: zod
|
|
71
|
+
.string()
|
|
72
|
+
.optional()
|
|
73
|
+
.describe(option({ description: "Updated on/before (ISO date)" })),
|
|
74
|
+
receiveFrom: zod
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe(option({ description: "Scheduled receive on/after (ISO date)" })),
|
|
78
|
+
receiveTo: zod
|
|
79
|
+
.string()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe(option({ description: "Scheduled receive on/before (ISO date)" })),
|
|
82
|
+
include: zod
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe(option({ description: "Relations to include, e.g. item" })),
|
|
86
|
+
});
|
|
87
|
+
function buildFilters(options) {
|
|
88
|
+
const filters = {
|
|
89
|
+
...viewFilters(options),
|
|
90
|
+
fulfillmentStatus: options.fulfillment,
|
|
91
|
+
receivalStatus: options.receival,
|
|
92
|
+
customerId: options.customerId,
|
|
93
|
+
itemId: options.itemId,
|
|
94
|
+
shopifyOrderId: options.shopifyOrderId,
|
|
95
|
+
shopifyVariantId: options.shopifyVariantId,
|
|
96
|
+
shopifyLineItemId: options.shopifyLineItemId,
|
|
97
|
+
returnOrderStatus: options.returnOrderStatus,
|
|
98
|
+
include: options.include,
|
|
99
|
+
};
|
|
100
|
+
if (options.unfulfilled)
|
|
101
|
+
filters.unfulfilled = true;
|
|
102
|
+
if (options.excludeCancelled)
|
|
103
|
+
filters.excludeCancelled = true;
|
|
104
|
+
// Date-range filters: --*-from maps to gte (inclusive), --*-to maps to lte.
|
|
105
|
+
if (options.rentalStartFrom || options.rentalStartTo) {
|
|
106
|
+
filters.rentalStart = {
|
|
107
|
+
gte: options.rentalStartFrom,
|
|
108
|
+
lte: options.rentalStartTo,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (options.createdFrom || options.createdTo) {
|
|
112
|
+
filters.created = { gte: options.createdFrom, lte: options.createdTo };
|
|
113
|
+
}
|
|
114
|
+
if (options.updatedSince || options.updatedTo) {
|
|
115
|
+
filters.updated = { gte: options.updatedSince, lte: options.updatedTo };
|
|
116
|
+
}
|
|
117
|
+
if (options.receiveFrom || options.receiveTo) {
|
|
118
|
+
filters.receiveAt = { gte: options.receiveFrom, lte: options.receiveTo };
|
|
119
|
+
}
|
|
120
|
+
return filters;
|
|
121
|
+
}
|
|
122
|
+
export default function List({ options }) {
|
|
123
|
+
return React.createElement(CyclesListView, { filters: buildFilters(options), all: options.all, json: options.json });
|
|
124
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { option } from "pastel";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import zod from "zod";
|
|
4
|
+
import CyclesListView from "../../components/CyclesListView.js";
|
|
5
|
+
import { listViewShape, viewFilters } from "../../lib/listViewOptions.js";
|
|
6
|
+
export const description = "Overdue cycles (returns by default, or dispatches)";
|
|
7
|
+
export const options = zod.object({
|
|
8
|
+
...listViewShape(),
|
|
9
|
+
type: zod
|
|
10
|
+
.enum(["receival", "fulfillment"])
|
|
11
|
+
.default("receival")
|
|
12
|
+
.describe(option({
|
|
13
|
+
description: "Which deadline is overdue: receival (returns) or fulfillment (dispatch)",
|
|
14
|
+
})),
|
|
15
|
+
});
|
|
16
|
+
export default function Overdue({ options }) {
|
|
17
|
+
const filters = { ...viewFilters(options) };
|
|
18
|
+
if (options.type === "fulfillment") {
|
|
19
|
+
filters.fulfillmentStatus = "overdue";
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
filters.receivalStatus = "overdue";
|
|
23
|
+
}
|
|
24
|
+
return React.createElement(CyclesListView, { filters: filters, all: options.all, json: options.json });
|
|
25
|
+
}
|