@easycustomerfeedback/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/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/index.js +344 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Bonner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# @easycustomerfeedback/cli
|
|
2
|
+
|
|
3
|
+
`ecf` is the command-line interface for [EasyCustomerFeedback](https://easycustomerfeedback.com). Triage feedback submissions, update statuses, and leave internal comments from your terminal.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g @easycustomerfeedback/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run without installing:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npx @easycustomerfeedback/cli --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Node.js 18 or newer.
|
|
18
|
+
|
|
19
|
+
## Getting started
|
|
20
|
+
|
|
21
|
+
1. In the EasyCustomerFeedback dashboard, open **Integrations → Personal API tokens** and create a token. Copy it — tokens are only shown once.
|
|
22
|
+
2. Log in:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
ecf login
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
You'll be prompted for the server URL (defaults to `https://easycustomerfeedback.com`) and the token. Credentials are saved to `~/.config/ecf/config.json`.
|
|
29
|
+
3. If you belong to multiple workspaces, pick a default:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
ecf workspace list
|
|
33
|
+
ecf workspace use <workspaceId>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
### Authentication
|
|
39
|
+
|
|
40
|
+
| Command | What it does |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `ecf login` | Configure server URL and API token. Auto-selects the workspace if you only have one. |
|
|
43
|
+
|
|
44
|
+
### Workspaces
|
|
45
|
+
|
|
46
|
+
| Command | What it does |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `ecf workspace list` | Show the workspaces you belong to (marks the active one). |
|
|
49
|
+
| `ecf workspace use <id>` | Set the default workspace for future commands. |
|
|
50
|
+
|
|
51
|
+
### Submissions
|
|
52
|
+
|
|
53
|
+
| Command | What it does |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `ecf submissions list [options]` | List submissions in the active workspace. |
|
|
56
|
+
| `ecf submissions get <id> [--json]` | Show details for a submission. |
|
|
57
|
+
| `ecf submissions status <id> <status>` | Update a submission's status. |
|
|
58
|
+
| `ecf submissions comment <id> [body]` | Add an internal comment (body may come from stdin). |
|
|
59
|
+
|
|
60
|
+
`ecf submissions list` options:
|
|
61
|
+
|
|
62
|
+
- `--status <s>` — filter by status (repeatable: `untriaged`, `open`, `in_progress`, `resolved`, `closed`)
|
|
63
|
+
- `--type <t>` — filter by type (`bug`, `feature_request`, `general_feedback`)
|
|
64
|
+
- `--project <id>` — filter by project
|
|
65
|
+
- `--limit <n>` — 1..200 (default 50)
|
|
66
|
+
- `--workspace <id>` — override the active workspace
|
|
67
|
+
- `--json` — output raw JSON
|
|
68
|
+
|
|
69
|
+
Valid status values for `ecf submissions status`: `untriaged`, `open`, `in_progress`, `resolved`, `closed`.
|
|
70
|
+
|
|
71
|
+
## Examples
|
|
72
|
+
|
|
73
|
+
List open bugs, piped to your pager:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
ecf submissions list --status open --type bug | less
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Pipe a longer comment in from a file:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
cat review.md | ecf submissions comment sub_abc123
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Resolve a submission:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
ecf submissions status sub_abc123 resolved
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
Configuration lives at `~/.config/ecf/config.json` and is created by `ecf login`. Delete the file to reset.
|
|
94
|
+
|
|
95
|
+
## API reference
|
|
96
|
+
|
|
97
|
+
The CLI is a thin wrapper around the public REST API. See [the API docs](https://easycustomerfeedback.com/docs/api) for everything the CLI exposes and more.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
[MIT](./LICENSE)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/login.ts
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
|
|
6
|
+
// src/api.ts
|
|
7
|
+
class ApiError extends Error {
|
|
8
|
+
status;
|
|
9
|
+
constructor(message, status) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function apiFetch(config, path, init = {}) {
|
|
15
|
+
const url = new URL(path, config.baseUrl).toString();
|
|
16
|
+
const headers = new Headers(init.headers);
|
|
17
|
+
headers.set("authorization", `Bearer ${config.apiKey}`);
|
|
18
|
+
if (init.body && !headers.has("content-type")) {
|
|
19
|
+
headers.set("content-type", "application/json");
|
|
20
|
+
}
|
|
21
|
+
const res = await fetch(url, { ...init, headers });
|
|
22
|
+
const text = await res.text();
|
|
23
|
+
let body = null;
|
|
24
|
+
if (text) {
|
|
25
|
+
try {
|
|
26
|
+
body = JSON.parse(text);
|
|
27
|
+
} catch {
|
|
28
|
+
body = text;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const message = typeof body === "object" && body !== null && "error" in body ? String(body.error) : `Request failed with status ${res.status}`;
|
|
33
|
+
throw new ApiError(message, res.status);
|
|
34
|
+
}
|
|
35
|
+
return body;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/config.ts
|
|
39
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
40
|
+
import { homedir } from "node:os";
|
|
41
|
+
import { dirname, join } from "node:path";
|
|
42
|
+
var CONFIG_DIR = join(homedir(), ".config", "ecf");
|
|
43
|
+
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
44
|
+
function loadConfig() {
|
|
45
|
+
if (!existsSync(CONFIG_PATH))
|
|
46
|
+
return null;
|
|
47
|
+
try {
|
|
48
|
+
const raw = readFileSync(CONFIG_PATH, "utf8");
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
if (typeof parsed?.baseUrl !== "string" || typeof parsed?.apiKey !== "string")
|
|
51
|
+
return null;
|
|
52
|
+
return parsed;
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function saveConfig(config) {
|
|
58
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
59
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
60
|
+
`, { mode: 384 });
|
|
61
|
+
}
|
|
62
|
+
function requireConfig() {
|
|
63
|
+
const config = loadConfig();
|
|
64
|
+
if (!config) {
|
|
65
|
+
console.error("Not logged in. Run: ecf login");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
return config;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/commands/login.ts
|
|
72
|
+
async function prompt(rl, question, def) {
|
|
73
|
+
const suffix = def ? ` [${def}]` : "";
|
|
74
|
+
const answer = (await rl.question(`${question}${suffix}: `)).trim();
|
|
75
|
+
return answer || def || "";
|
|
76
|
+
}
|
|
77
|
+
async function login() {
|
|
78
|
+
const existing = loadConfig();
|
|
79
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
80
|
+
try {
|
|
81
|
+
const baseUrl = await prompt(rl, "Server URL", existing?.baseUrl ?? "https://easycustomerfeedback.com");
|
|
82
|
+
const apiKey = await prompt(rl, "API token (ecf_…)");
|
|
83
|
+
if (!baseUrl || !apiKey) {
|
|
84
|
+
console.error("Server URL and API token are both required.");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
const config = { baseUrl, apiKey, workspaceId: existing?.workspaceId };
|
|
88
|
+
const { workspaces } = await apiFetch(config, "/api/v1/workspaces");
|
|
89
|
+
if (workspaces.length === 1) {
|
|
90
|
+
config.workspaceId = workspaces[0].id;
|
|
91
|
+
}
|
|
92
|
+
saveConfig(config);
|
|
93
|
+
console.log(`
|
|
94
|
+
Saved to ${CONFIG_PATH}`);
|
|
95
|
+
console.log(`You belong to ${workspaces.length} workspace(s):`);
|
|
96
|
+
for (const w of workspaces) {
|
|
97
|
+
const marker = w.id === config.workspaceId ? "* " : " ";
|
|
98
|
+
console.log(`${marker}${w.id} ${w.name}`);
|
|
99
|
+
}
|
|
100
|
+
if (workspaces.length === 1) {
|
|
101
|
+
console.log(`
|
|
102
|
+
Active workspace set to ${workspaces[0].name}.`);
|
|
103
|
+
} else if (!config.workspaceId && workspaces.length > 1) {
|
|
104
|
+
console.log("\nTip: pick a default with `ecf workspace use <id>`");
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
rl.close();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/commands/submissions.ts
|
|
112
|
+
import { parseArgs } from "node:util";
|
|
113
|
+
function resolveWorkspace(config, override) {
|
|
114
|
+
const id = override ?? config.workspaceId;
|
|
115
|
+
if (!id) {
|
|
116
|
+
console.error("No workspace selected. Pass --workspace <id> or set a default with `ecf workspace use <id>`.");
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
return id;
|
|
120
|
+
}
|
|
121
|
+
async function readStdin() {
|
|
122
|
+
const chunks = [];
|
|
123
|
+
for await (const chunk of process.stdin) {
|
|
124
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
125
|
+
}
|
|
126
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
127
|
+
}
|
|
128
|
+
async function submissionsCommand(args) {
|
|
129
|
+
const [sub, ...rest] = args;
|
|
130
|
+
if (sub === "list")
|
|
131
|
+
return list(rest);
|
|
132
|
+
if (sub === "get")
|
|
133
|
+
return get(rest);
|
|
134
|
+
if (sub === "status")
|
|
135
|
+
return status(rest);
|
|
136
|
+
if (sub === "comment")
|
|
137
|
+
return comment(rest);
|
|
138
|
+
console.error("Usage: ecf submissions <list|get|status|comment>");
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
async function list(args) {
|
|
142
|
+
const { values } = parseArgs({
|
|
143
|
+
args,
|
|
144
|
+
options: {
|
|
145
|
+
status: { type: "string", multiple: true },
|
|
146
|
+
type: { type: "string" },
|
|
147
|
+
project: { type: "string" },
|
|
148
|
+
limit: { type: "string" },
|
|
149
|
+
workspace: { type: "string" },
|
|
150
|
+
json: { type: "boolean" }
|
|
151
|
+
},
|
|
152
|
+
allowPositionals: false
|
|
153
|
+
});
|
|
154
|
+
const config = requireConfig();
|
|
155
|
+
const workspaceId = resolveWorkspace(config, values.workspace);
|
|
156
|
+
const params = new URLSearchParams({ workspaceId });
|
|
157
|
+
for (const s of values.status ?? [])
|
|
158
|
+
params.append("status", s);
|
|
159
|
+
if (values.type)
|
|
160
|
+
params.set("type", values.type);
|
|
161
|
+
if (values.project)
|
|
162
|
+
params.set("projectId", values.project);
|
|
163
|
+
if (values.limit)
|
|
164
|
+
params.set("limit", values.limit);
|
|
165
|
+
const { submissions } = await apiFetch(config, `/api/v1/submissions?${params.toString()}`);
|
|
166
|
+
if (values.json) {
|
|
167
|
+
console.log(JSON.stringify(submissions, null, 2));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (submissions.length === 0) {
|
|
171
|
+
console.log("No submissions match.");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
for (const s of submissions) {
|
|
175
|
+
const submitter = s.submitterName ?? s.submitterEmail ?? "anonymous";
|
|
176
|
+
console.log(`${s.id} [${s.status.padEnd(11)}] ${s.type.padEnd(18)} ${s.projectName} · ${s.title} (${submitter})`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function get(args) {
|
|
180
|
+
const { values, positionals } = parseArgs({
|
|
181
|
+
args,
|
|
182
|
+
options: { json: { type: "boolean" } },
|
|
183
|
+
allowPositionals: true
|
|
184
|
+
});
|
|
185
|
+
const [id] = positionals;
|
|
186
|
+
if (!id) {
|
|
187
|
+
console.error("Usage: ecf submissions get <id> [--json]");
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
const config = requireConfig();
|
|
191
|
+
const { submission } = await apiFetch(config, `/api/v1/submissions/${encodeURIComponent(id)}`);
|
|
192
|
+
if (values.json) {
|
|
193
|
+
console.log(JSON.stringify(submission, null, 2));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
console.log(`# ${submission.title}`);
|
|
197
|
+
console.log(`id: ${submission.id} type: ${submission.type} status: ${submission.status} priority: ${submission.priority}`);
|
|
198
|
+
console.log(`project: ${submission.projectName} workspace: ${submission.workspaceName}`);
|
|
199
|
+
const submitter = submission.submitterName ?? submission.submitterEmail ?? "anonymous";
|
|
200
|
+
console.log(`from: ${submitter} created: ${submission.createdAt}`);
|
|
201
|
+
if (submission.sourceUrl)
|
|
202
|
+
console.log(`url: ${submission.sourceUrl}`);
|
|
203
|
+
if (submission.assignee) {
|
|
204
|
+
console.log(`assigned: ${submission.assignee.name ?? submission.assignee.email}`);
|
|
205
|
+
}
|
|
206
|
+
console.log("");
|
|
207
|
+
console.log(submission.description);
|
|
208
|
+
if (submission.comments.length > 0) {
|
|
209
|
+
console.log(`
|
|
210
|
+
— comments —`);
|
|
211
|
+
for (const c of submission.comments) {
|
|
212
|
+
const who = c.author.name ?? c.author.email ?? "unknown";
|
|
213
|
+
console.log(`
|
|
214
|
+
${who} ${c.createdAt}`);
|
|
215
|
+
console.log(c.body);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (submission.attachments.length > 0) {
|
|
219
|
+
console.log(`
|
|
220
|
+
— attachments —`);
|
|
221
|
+
for (const a of submission.attachments) {
|
|
222
|
+
console.log(`${a.fileName} ${a.downloadUrl}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async function status(args) {
|
|
227
|
+
const [id, newStatus] = args;
|
|
228
|
+
if (!id || !newStatus) {
|
|
229
|
+
console.error("Usage: ecf submissions status <id> <untriaged|open|in_progress|resolved|closed>");
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
const config = requireConfig();
|
|
233
|
+
await apiFetch(config, `/api/v1/submissions/${encodeURIComponent(id)}`, {
|
|
234
|
+
method: "PATCH",
|
|
235
|
+
body: JSON.stringify({ status: newStatus })
|
|
236
|
+
});
|
|
237
|
+
console.log(`Updated ${id} → ${newStatus}`);
|
|
238
|
+
}
|
|
239
|
+
async function comment(args) {
|
|
240
|
+
const [id, ...bodyParts] = args;
|
|
241
|
+
if (!id) {
|
|
242
|
+
console.error("Usage: ecf submissions comment <id> [body] (body may also come via stdin)");
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
let body = bodyParts.join(" ").trim();
|
|
246
|
+
if (!body && !process.stdin.isTTY) {
|
|
247
|
+
body = await readStdin();
|
|
248
|
+
}
|
|
249
|
+
if (!body) {
|
|
250
|
+
console.error("Comment body is required (positional arg or stdin).");
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
const config = requireConfig();
|
|
254
|
+
const result = await apiFetch(config, `/api/v1/submissions/${encodeURIComponent(id)}/comments`, { method: "POST", body: JSON.stringify({ body }) });
|
|
255
|
+
console.log(`Added comment ${result.comment.id}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/commands/workspace.ts
|
|
259
|
+
async function workspaceCommand(args) {
|
|
260
|
+
const [sub, ...rest] = args;
|
|
261
|
+
if (sub === "list") {
|
|
262
|
+
const config = requireConfig();
|
|
263
|
+
const { workspaces } = await apiFetch(config, "/api/v1/workspaces");
|
|
264
|
+
if (workspaces.length === 0) {
|
|
265
|
+
console.log("No workspaces.");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const activeId = config.workspaceId;
|
|
269
|
+
for (const w of workspaces) {
|
|
270
|
+
const marker = w.id === activeId ? "* " : " ";
|
|
271
|
+
console.log(`${marker}${w.id} ${w.name}`);
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (sub === "use") {
|
|
276
|
+
const config = requireConfig();
|
|
277
|
+
const [workspaceId] = rest;
|
|
278
|
+
if (!workspaceId) {
|
|
279
|
+
console.error("Usage: ecf workspace use <workspaceId>");
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
saveConfig({ ...config, workspaceId });
|
|
283
|
+
console.log(`Active workspace set to ${workspaceId}`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
console.error("Usage: ecf workspace <list|use>");
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/index.ts
|
|
291
|
+
var USAGE = `ecf — EasyCustomerFeedback CLI
|
|
292
|
+
|
|
293
|
+
Usage:
|
|
294
|
+
ecf login Configure API token and server
|
|
295
|
+
ecf workspace list List workspaces you belong to
|
|
296
|
+
ecf workspace use <workspaceId> Set the default workspace
|
|
297
|
+
|
|
298
|
+
ecf submissions list [options] List submissions in the active workspace
|
|
299
|
+
--status <s> (repeatable: untriaged|open|in_progress|resolved|closed)
|
|
300
|
+
--type <t> bug | feature_request | general_feedback
|
|
301
|
+
--project <id> Filter by project id
|
|
302
|
+
--limit <n> 1..200 (default 50)
|
|
303
|
+
--workspace <id> Override active workspace
|
|
304
|
+
--json Output raw JSON
|
|
305
|
+
|
|
306
|
+
ecf submissions get <id> [--json] Show details for a submission
|
|
307
|
+
ecf submissions status <id> <status> Update a submission's status
|
|
308
|
+
ecf submissions comment <id> [body] Add an internal comment
|
|
309
|
+
(if body is omitted, reads from stdin)
|
|
310
|
+
`;
|
|
311
|
+
async function main() {
|
|
312
|
+
const argv = process.argv.slice(2);
|
|
313
|
+
const [command, ...rest] = argv;
|
|
314
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
315
|
+
process.stdout.write(USAGE);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
switch (command) {
|
|
320
|
+
case "login":
|
|
321
|
+
await login();
|
|
322
|
+
return;
|
|
323
|
+
case "workspace":
|
|
324
|
+
await workspaceCommand(rest);
|
|
325
|
+
return;
|
|
326
|
+
case "submissions":
|
|
327
|
+
await submissionsCommand(rest);
|
|
328
|
+
return;
|
|
329
|
+
default:
|
|
330
|
+
console.error(`Unknown command: ${command}
|
|
331
|
+
`);
|
|
332
|
+
process.stdout.write(USAGE);
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
} catch (err) {
|
|
336
|
+
if (err instanceof Error) {
|
|
337
|
+
console.error(`Error: ${err.message}`);
|
|
338
|
+
} else {
|
|
339
|
+
console.error("Unknown error", err);
|
|
340
|
+
}
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
await main();
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@easycustomerfeedback/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Command-line interface for EasyCustomerFeedback — list, triage, and comment on feedback submissions from your terminal.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ecf": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/michaelbonner/easycustomerfeedback.git",
|
|
25
|
+
"directory": "packages/cli"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://easycustomerfeedback.com",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/michaelbonner/easycustomerfeedback/issues"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"easycustomerfeedback",
|
|
33
|
+
"ecf",
|
|
34
|
+
"cli",
|
|
35
|
+
"feedback",
|
|
36
|
+
"customer-feedback"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"dev": "bun run src/index.ts",
|
|
40
|
+
"build": "bun run build.ts",
|
|
41
|
+
"typecheck": "tsc --noEmit",
|
|
42
|
+
"prepublishOnly": "bun run build"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"typescript": "^6.0.2"
|
|
46
|
+
}
|
|
47
|
+
}
|