crispctl 0.0.0 → 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/dist/help.js ADDED
@@ -0,0 +1,175 @@
1
+ export const usage = {
2
+ authSet: "crispctl auth set [--profile <name>] --identifier <id> --key <key> --tier <website|plugin> --website <website_id>",
3
+ authShow: "crispctl auth show [--profile <name>]",
4
+ conversationsList: "crispctl conversations list [--page <n>]",
5
+ conversationsGet: "crispctl conversations get <session>",
6
+ conversationsSearch: "crispctl conversations search <query> [--page <n>] [--search-type text|segment]",
7
+ messagesList: "crispctl messages list <session>",
8
+ reply: "crispctl reply <session> (--text <message> | --note <note>)",
9
+ resolve: "crispctl resolve <session>",
10
+ reopen: "crispctl reopen <session>",
11
+ assign: "crispctl assign <session> (--user <id> | --unassign)",
12
+ segments: "crispctl segments <session> --set <a,b>",
13
+ read: "crispctl read <session>",
14
+ peopleGet: "crispctl people get <id|email>",
15
+ operatorsList: "crispctl operators list",
16
+ listen: "crispctl listen",
17
+ };
18
+ const globals = "Global flags: --json, --profile <name>, --website <id>";
19
+ export const ROOT_HELP = `crispctl — agent-friendly Crisp REST CLI
20
+
21
+ Usage:
22
+ crispctl [--json] [--profile <name>] [--website <id>] <command> [args]
23
+
24
+ ${globals}
25
+
26
+ Commands:
27
+ auth set Save identifier, key, tier, and website_id
28
+ auth show Show the resolved profile (key redacted)
29
+ conversations list List conversations
30
+ conversations get Get one conversation
31
+ conversations search Search conversations
32
+ messages list List messages in a session
33
+ reply Send an operator message or private note
34
+ resolve Set conversation state to resolved
35
+ reopen Set conversation state to unresolved
36
+ assign Assign or unassign an operator
37
+ segments Replace conversation segments
38
+ read Mark the conversation read for the operator
39
+ people get Get a people profile by id or email
40
+ operators list List website operators
41
+ listen Stub. RTM is not implemented
42
+
43
+ Profiles:
44
+ Named profiles live in the config file. "default" and "sandbox" are the usual names.
45
+ --profile and CRISPCTL_PROFILE select one. The file remembers the last profile written by auth set.
46
+
47
+ Environment (overrides the selected profile):
48
+ CRISPCTL_PROFILE
49
+ CRISPCTL_IDENTIFIER or CRISP_IDENTIFIER
50
+ CRISPCTL_KEY or CRISP_KEY
51
+ CRISPCTL_WEBSITE_ID or CRISP_WEBSITE_ID
52
+ CRISPCTL_TIER or CRISP_TIER
53
+ CRISPCTL_CONFIG Config file path (default: $XDG_CONFIG_HOME/crispctl/config.json)
54
+
55
+ The token key is never printed. A config file written by crispctl is mode 0600.
56
+
57
+ Sandbox: use the Cos Crisp sandbox website only. Never point crispctl at production Teachify.
58
+ `;
59
+ const HELP = {
60
+ auth: `crispctl auth
61
+
62
+ auth set Save a profile
63
+ auth show Show the resolved profile with the key redacted
64
+
65
+ ${usage.authSet}
66
+ ${usage.authShow}
67
+ `,
68
+ "auth set": `${usage.authSet}
69
+
70
+ Writes the selected profile and makes it current. Flags win, then CRISPCTL_* / CRISP_* env, then the existing profile.
71
+ --website and --website-id both set website_id. Tier is website or plugin.
72
+ `,
73
+ "auth show": `${usage.authShow}
74
+
75
+ Prints profile, identifier, tier, website_id, whether the key is set, and where each value came from.
76
+ The key value is not included.
77
+ `,
78
+ conversations: `crispctl conversations
79
+
80
+ conversations list
81
+ conversations get <session>
82
+ conversations search <query>
83
+ `,
84
+ "conversations list": `${usage.conversationsList}
85
+
86
+ GET /v1/website/{website_id}/conversations/{page}
87
+ `,
88
+ "conversations get": `${usage.conversationsGet}
89
+
90
+ GET /v1/website/{website_id}/conversation/{session}
91
+ `,
92
+ "conversations search": `${usage.conversationsSearch}
93
+
94
+ GET /v1/website/{website_id}/conversations/{page}?search_query&search_type
95
+ search_type is text (default) or segment.
96
+ `,
97
+ messages: `crispctl messages list <session>
98
+
99
+ ${usage.messagesList}
100
+ `,
101
+ "messages list": `${usage.messagesList}
102
+
103
+ GET /v1/website/{website_id}/conversation/{session}/messages
104
+ `,
105
+ reply: `${usage.reply}
106
+
107
+ POST /v1/website/{website_id}/conversation/{session}/message
108
+ --text sends {"type":"text","from":"operator","origin":"chat","content":"..."}
109
+ --note sends {"type":"note","from":"operator","origin":"chat","content":"..."}
110
+ `,
111
+ resolve: `${usage.resolve}
112
+
113
+ PATCH /v1/website/{website_id}/conversation/{session}/state
114
+ Body: {"state":"resolved"}
115
+ `,
116
+ reopen: `${usage.reopen}
117
+
118
+ PATCH /v1/website/{website_id}/conversation/{session}/state
119
+ Body: {"state":"unresolved"}
120
+ `,
121
+ assign: `${usage.assign}
122
+
123
+ PATCH /v1/website/{website_id}/conversation/{session}/routing
124
+ --user sends {"assigned":{"user_id":"<id>"}}
125
+ --unassign sends {"assigned":null}
126
+ `,
127
+ segments: `${usage.segments}
128
+
129
+ PATCH /v1/website/{website_id}/conversation/{session}/meta
130
+ Body: {"segments":["a","b"]}
131
+ `,
132
+ read: `${usage.read}
133
+
134
+ PATCH /v1/website/{website_id}/conversation/{session}/read
135
+ Body: {"from":"operator","origin":"chat"}
136
+ `,
137
+ people: `crispctl people get <id|email>
138
+
139
+ ${usage.peopleGet}
140
+ `,
141
+ "people get": `${usage.peopleGet}
142
+
143
+ A people id calls GET /v1/website/{website_id}/people/profile/{people_id}.
144
+ An email is not placed on that path. crispctl searches
145
+ GET /v1/website/{website_id}/people/profiles/1?search_text=<email>
146
+ and then fetches the people_id whose email matches exactly.
147
+ `,
148
+ operators: `crispctl operators list
149
+
150
+ ${usage.operatorsList}
151
+ `,
152
+ "operators list": `${usage.operatorsList}
153
+
154
+ GET /v1/website/{website_id}/operators/list
155
+ `,
156
+ listen: `${usage.listen}
157
+
158
+ RTM listen is not implemented. See the command output for the website-tier connect-endpoints pitfall.
159
+ `,
160
+ };
161
+ export function renderHelp(positionals) {
162
+ const key = positionals.join(" ");
163
+ if (!key) {
164
+ return ROOT_HELP;
165
+ }
166
+ if (HELP[key]) {
167
+ return HELP[key];
168
+ }
169
+ const head = positionals[0];
170
+ if (head && HELP[head]) {
171
+ return HELP[head];
172
+ }
173
+ return `${ROOT_HELP}\nUnknown command: ${positionals[0] ?? ""}\n`;
174
+ }
175
+ //# sourceMappingURL=help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,OAAO,EAAE,mHAAmH;IAC5H,QAAQ,EAAE,uCAAuC;IACjD,iBAAiB,EAAE,0CAA0C;IAC7D,gBAAgB,EAAE,sCAAsC;IACxD,mBAAmB,EAAE,iFAAiF;IACtG,YAAY,EAAE,kCAAkC;IAChD,KAAK,EAAE,6DAA6D;IACpE,OAAO,EAAE,4BAA4B;IACrC,MAAM,EAAE,2BAA2B;IACnC,MAAM,EAAE,sDAAsD;IAC9D,QAAQ,EAAE,yCAAyC;IACnD,IAAI,EAAE,yBAAyB;IAC/B,SAAS,EAAE,gCAAgC;IAC3C,aAAa,EAAE,yBAAyB;IACxC,MAAM,EAAE,iBAAiB;CACjB,CAAA;AAEV,MAAM,OAAO,GAAG,wDAAwD,CAAA;AAExE,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;EAKvB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCR,CAAA;AAED,MAAM,IAAI,GAA2B;IACnC,IAAI,EAAE;;;;;EAKN,KAAK,CAAC,OAAO;EACb,KAAK,CAAC,QAAQ;CACf;IACC,UAAU,EAAE,GAAG,KAAK,CAAC,OAAO;;;;CAI7B;IACC,WAAW,EAAE,GAAG,KAAK,CAAC,QAAQ;;;;CAI/B;IACC,aAAa,EAAE;;;;;CAKhB;IACC,oBAAoB,EAAE,GAAG,KAAK,CAAC,iBAAiB;;;CAGjD;IACC,mBAAmB,EAAE,GAAG,KAAK,CAAC,gBAAgB;;;CAG/C;IACC,sBAAsB,EAAE,GAAG,KAAK,CAAC,mBAAmB;;;;CAIrD;IACC,QAAQ,EAAE;;EAEV,KAAK,CAAC,YAAY;CACnB;IACC,eAAe,EAAE,GAAG,KAAK,CAAC,YAAY;;;CAGvC;IACC,KAAK,EAAE,GAAG,KAAK,CAAC,KAAK;;;;;CAKtB;IACC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO;;;;CAI1B;IACC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM;;;;CAIxB;IACC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM;;;;;CAKxB;IACC,QAAQ,EAAE,GAAG,KAAK,CAAC,QAAQ;;;;CAI5B;IACC,IAAI,EAAE,GAAG,KAAK,CAAC,IAAI;;;;CAIpB;IACC,MAAM,EAAE;;EAER,KAAK,CAAC,SAAS;CAChB;IACC,YAAY,EAAE,GAAG,KAAK,CAAC,SAAS;;;;;;CAMjC;IACC,SAAS,EAAE;;EAEX,KAAK,CAAC,aAAa;CACpB;IACC,gBAAgB,EAAE,GAAG,KAAK,CAAC,aAAa;;;CAGzC;IACC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM;;;CAGxB;CACA,CAAA;AAED,MAAM,UAAU,UAAU,CAAC,WAA8B;IACvD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACjC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACd,OAAO,IAAI,CAAC,GAAG,CAAC,CAAA;IAClB,CAAC;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAC3B,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAA;IACnB,CAAC;IACD,OAAO,GAAG,SAAS,sBAAsB,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAA;AACnE,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function isDirectInvocation(entry?: string, moduleUrl?: string): boolean;
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
3
+ import { pathToFileURL } from "node:url";
4
+ import { run } from "./cli.js";
5
+ export function isDirectInvocation(entry = process.argv[1], moduleUrl = import.meta.url) {
6
+ if (!entry)
7
+ return false;
8
+ try {
9
+ return moduleUrl === pathToFileURL(realpathSync(entry)).href;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ if (isDirectInvocation()) {
16
+ process.exitCode = await run(process.argv.slice(2));
17
+ }
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAE9B,MAAM,UAAU,kBAAkB,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG;IACrF,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IACxB,IAAI,CAAC;QACH,OAAO,SAAS,KAAK,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,IAAI,kBAAkB,EAAE,EAAE,CAAC;IACzB,OAAO,CAAC,QAAQ,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AACrD,CAAC"}
@@ -0,0 +1,11 @@
1
+ export type ErrorPayload = {
2
+ ok: false;
3
+ error: string;
4
+ message: string;
5
+ status?: number;
6
+ reason?: string;
7
+ retry_after?: string;
8
+ };
9
+ export declare function writeOut(stdout: (chunk: string) => void, json: boolean, value: unknown): void;
10
+ export declare function errorPayload(err: unknown, secrets: readonly (string | undefined)[]): ErrorPayload;
11
+ export declare function writeErr(stderr: (chunk: string) => void, json: boolean, err: unknown, secrets: readonly (string | undefined)[]): void;
package/dist/output.js ADDED
@@ -0,0 +1,50 @@
1
+ import { CrispApiError, ConfigError, UsageError } from "./errors.js";
2
+ import { redactSecrets } from "./redact.js";
3
+ export function writeOut(stdout, json, value) {
4
+ if (!json && typeof value === "string") {
5
+ stdout(value.endsWith("\n") ? value : `${value}\n`);
6
+ return;
7
+ }
8
+ stdout(`${json ? JSON.stringify(value) : JSON.stringify(value, null, 2)}\n`);
9
+ }
10
+ export function errorPayload(err, secrets) {
11
+ if (err instanceof CrispApiError) {
12
+ const payload = {
13
+ ok: false,
14
+ error: err.reason,
15
+ status: err.status,
16
+ reason: err.reason,
17
+ message: redactSecrets(err.message, secrets),
18
+ };
19
+ if (err.retryAfter) {
20
+ payload.retry_after = err.retryAfter;
21
+ }
22
+ return payload;
23
+ }
24
+ if (err instanceof UsageError) {
25
+ return { ok: false, error: "usage", message: redactSecrets(err.message, secrets) };
26
+ }
27
+ if (err instanceof ConfigError) {
28
+ return { ok: false, error: "config", message: redactSecrets(err.message, secrets) };
29
+ }
30
+ const message = err instanceof Error ? err.message : "unknown error";
31
+ return { ok: false, error: "error", message: redactSecrets(message, secrets) };
32
+ }
33
+ export function writeErr(stderr, json, err, secrets) {
34
+ const payload = errorPayload(err, secrets);
35
+ if (json) {
36
+ stderr(`${JSON.stringify(payload)}\n`);
37
+ return;
38
+ }
39
+ if ((payload.error === "usage" || payload.error === "config") && payload.status === undefined) {
40
+ stderr(`error: ${payload.message}\n`);
41
+ return;
42
+ }
43
+ const head = [payload.status, payload.reason || payload.error].filter((part) => part !== undefined && part !== "").join(" ");
44
+ if (payload.message && payload.message !== payload.reason && payload.message !== payload.error) {
45
+ stderr(`error: ${head}: ${payload.message}\n`);
46
+ return;
47
+ }
48
+ stderr(`error: ${head}\n`);
49
+ }
50
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function redactSecrets(message: string, secrets: readonly (string | undefined)[]): string;
package/dist/redact.js ADDED
@@ -0,0 +1,12 @@
1
+ export function redactSecrets(message, secrets) {
2
+ const present = [...new Set(secrets.filter((secret) => typeof secret === "string" && secret.length > 0))];
3
+ if (present.length === 0)
4
+ return message;
5
+ present.sort((left, right) => right.length - left.length);
6
+ const pattern = new RegExp(present.map(escapeRegExp).join("|"), "g");
7
+ return message.replace(pattern, "[redacted]");
8
+ }
9
+ function escapeRegExp(value) {
10
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11
+ }
12
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,OAAwC;IACrF,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAoB,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3H,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAA;IACxC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;IACzD,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAA;IACpE,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;AAC/C,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;AACrD,CAAC"}
package/dist/rtm.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export declare const SDK_WEBSITE_TIER_CONNECT_PATH = "/v1/website/connect/endpoints";
2
+ export declare const PLUGIN_CONNECT_PATH = "/v1/plugin/connect/endpoints";
3
+ export declare function connectEndpointsPath(tier: "website" | "plugin", websiteId: string): string;
4
+ export declare function listenStubMessage(websiteId?: string): string;
package/dist/rtm.js ADDED
@@ -0,0 +1,25 @@
1
+ export const SDK_WEBSITE_TIER_CONNECT_PATH = "/v1/website/connect/endpoints";
2
+ export const PLUGIN_CONNECT_PATH = "/v1/plugin/connect/endpoints";
3
+ export function connectEndpointsPath(tier, websiteId) {
4
+ if (tier === "plugin") {
5
+ return PLUGIN_CONNECT_PATH;
6
+ }
7
+ return `/v1/website/${encodeURIComponent(websiteId)}/connect/endpoints`;
8
+ }
9
+ export function listenStubMessage(websiteId) {
10
+ const correct = websiteId
11
+ ? connectEndpointsPath("website", websiteId)
12
+ : "/v1/website/{website_id}/connect/endpoints";
13
+ return [
14
+ "listen is not implemented (RTM is a follow-up).",
15
+ "",
16
+ "Website-tier pitfall: the official crisp-api Node SDK requests",
17
+ `GET ${SDK_WEBSITE_TIER_CONNECT_PATH}`,
18
+ "when the token tier is \"website\". That call omits website_id and does not work.",
19
+ `The correct website route is GET ${correct}.`,
20
+ `Plugin tier uses GET ${PLUGIN_CONNECT_PATH}, which the SDK does call.`,
21
+ "",
22
+ "Use the Cos Sandbox website only. Never point listen at production Teachify.",
23
+ ].join("\n");
24
+ }
25
+ //# sourceMappingURL=rtm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rtm.js","sourceRoot":"","sources":["../src/rtm.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,6BAA6B,GAAG,+BAA+B,CAAA;AAE5E,MAAM,CAAC,MAAM,mBAAmB,GAAG,8BAA8B,CAAA;AAEjE,MAAM,UAAU,oBAAoB,CAAC,IAA0B,EAAE,SAAiB;IAChF,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO,mBAAmB,CAAA;IAC5B,CAAC;IACD,OAAO,eAAe,kBAAkB,CAAC,SAAS,CAAC,oBAAoB,CAAA;AACzE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,SAAkB;IAClD,MAAM,OAAO,GAAG,SAAS;QACvB,CAAC,CAAC,oBAAoB,CAAC,SAAS,EAAE,SAAS,CAAC;QAC5C,CAAC,CAAC,4CAA4C,CAAA;IAChD,OAAO;QACL,iDAAiD;QACjD,EAAE;QACF,gEAAgE;QAChE,OAAO,6BAA6B,EAAE;QACtC,mFAAmF;QACnF,oCAAoC,OAAO,GAAG;QAC9C,wBAAwB,mBAAmB,4BAA4B;QACvE,EAAE;QACF,8EAA8E;KAC/E,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACd,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const version: string;
@@ -0,0 +1,7 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const here = dirname(fileURLToPath(import.meta.url));
5
+ const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
6
+ export const version = pkg.version;
7
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AACpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAwB,CAAA;AAErG,MAAM,CAAC,MAAM,OAAO,GAAW,GAAG,CAAC,OAAO,CAAA"}
package/package.json CHANGED
@@ -1,8 +1,57 @@
1
1
  {
2
2
  "name": "crispctl",
3
- "version": "0.0.0",
4
- "description": "Placeholder — crispctl is under development.",
3
+ "version": "0.1.0",
4
+ "description": "Agent-friendly CLI for Crisp support operations",
5
5
  "license": "MIT",
6
6
  "author": "linyiru",
7
- "files": ["README.md"]
7
+ "type": "module",
8
+ "bin": {
9
+ "crispctl": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/solcreek/crisp-cli.git"
22
+ },
23
+ "homepage": "https://github.com/solcreek/crisp-cli#readme",
24
+ "bugs": "https://github.com/solcreek/crisp-cli/issues",
25
+ "keywords": [
26
+ "crisp",
27
+ "cli",
28
+ "support"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "typecheck": "tsc -p tsconfig.test.json --noEmit",
33
+ "test": "c8 node --enable-source-maps --import tsx --test test/*.test.ts",
34
+ "test:live": "node --import tsx --test test/live/smoke.test.ts",
35
+ "prepublishOnly": "npm run build"
36
+ },
37
+ "dependencies": {
38
+ "undici": "^6.21.2"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.15.3",
42
+ "c8": "^10.1.3",
43
+ "tsx": "^4.20.3",
44
+ "typescript": "^5.8.3"
45
+ },
46
+ "c8": {
47
+ "all": true,
48
+ "src": ["src"],
49
+ "include": ["src/**/*.ts"],
50
+ "reporter": ["text", "text-summary"],
51
+ "check-coverage": true,
52
+ "lines": 80,
53
+ "statements": 80,
54
+ "functions": 80,
55
+ "branches": 75
56
+ }
8
57
  }