@aaronshaf/plane 0.1.8 → 0.1.11

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.8",
6
+ "version": "0.1.11",
7
7
  "description": "CLI for the Plane project management API",
8
8
  "keywords": [
9
9
  "plane",
package/src/api.ts CHANGED
@@ -66,7 +66,22 @@ function request(
66
66
  // 204 No Content
67
67
  if (res.status === 204) return null;
68
68
 
69
- return res.json();
69
+ // Use text + lenient parse to handle bare control characters (U+0000–U+001F)
70
+ // that may appear inside JSON string values (e.g. description_html with \n in <pre>).
71
+ const text = await res.text();
72
+ try {
73
+ return JSON.parse(text);
74
+ } catch {
75
+ // Escape bare control characters inside JSON string values and retry.
76
+ const sanitized = text.replace(
77
+ /"(?:[^"\\]|\\.)*"/g,
78
+ (match) => match.replace(/[\x00-\x1F]/g, (c) => {
79
+ const hex = c.charCodeAt(0).toString(16).padStart(4, "0");
80
+ return `\\u${hex}`;
81
+ }),
82
+ );
83
+ return JSON.parse(sanitized);
84
+ }
70
85
  },
71
86
  catch: (e) => (e instanceof Error ? e : new Error(String(e))),
72
87
  });
@@ -15,34 +15,37 @@ const cycleIdArg = Args.text({ name: "cycle-id" }).pipe(
15
15
 
16
16
  // --- cycles list ---
17
17
 
18
+ export function cyclesListHandler({ project }: { project: string }) {
19
+ return Effect.gen(function* () {
20
+ const { id } = yield* resolveProject(project);
21
+ const raw = yield* api.get(`projects/${id}/cycles/`);
22
+ const { results } = yield* decodeOrFail(CyclesResponseSchema, raw);
23
+ if (jsonMode) {
24
+ yield* Console.log(JSON.stringify(results, null, 2));
25
+ return;
26
+ }
27
+ if (xmlMode) {
28
+ yield* Console.log(toXml(results));
29
+ return;
30
+ }
31
+ if (results.length === 0) {
32
+ yield* Console.log("No cycles found");
33
+ return;
34
+ }
35
+ const lines = results.map((c) => {
36
+ const start = c.start_date ?? "—";
37
+ const end = c.end_date ?? "—";
38
+ const status = (c.status ?? "?").padEnd(10);
39
+ return `${c.id} ${status} ${start} → ${end} ${c.name}`;
40
+ });
41
+ yield* Console.log(lines.join("\n"));
42
+ });
43
+ }
44
+
18
45
  export const cyclesList = Command.make(
19
46
  "list",
20
47
  { project: projectArg },
21
- ({ project }) =>
22
- Effect.gen(function* () {
23
- const { id } = yield* resolveProject(project);
24
- const raw = yield* api.get(`projects/${id}/cycles/`);
25
- const { results } = yield* decodeOrFail(CyclesResponseSchema, raw);
26
- if (jsonMode) {
27
- yield* Console.log(JSON.stringify(results, null, 2));
28
- return;
29
- }
30
- if (xmlMode) {
31
- yield* Console.log(toXml(results));
32
- return;
33
- }
34
- if (results.length === 0) {
35
- yield* Console.log("No cycles found");
36
- return;
37
- }
38
- const lines = results.map((c) => {
39
- const start = c.start_date ?? "—";
40
- const end = c.end_date ?? "—";
41
- const status = (c.status ?? "?").padEnd(10);
42
- return `${c.id} ${status} ${start} → ${end} ${c.name}`;
43
- });
44
- yield* Console.log(lines.join("\n"));
45
- }),
48
+ cyclesListHandler,
46
49
  ).pipe(
47
50
  Command.withDescription(
48
51
  "List cycles for a project. Shows cycle UUID, status, date range, and name.\n\nExample:\n plane cycles list PROJ",
@@ -51,37 +54,46 @@ export const cyclesList = Command.make(
51
54
 
52
55
  // --- cycles issues list ---
53
56
 
57
+ export function cycleIssuesListHandler({
58
+ project,
59
+ cycleId,
60
+ }: {
61
+ project: string;
62
+ cycleId: string;
63
+ }) {
64
+ return Effect.gen(function* () {
65
+ const { key, id } = yield* resolveProject(project);
66
+ const raw = yield* api.get(
67
+ `projects/${id}/cycles/${cycleId}/cycle-issues/`,
68
+ );
69
+ const { results } = yield* decodeOrFail(CycleIssuesResponseSchema, raw);
70
+ if (jsonMode) {
71
+ yield* Console.log(JSON.stringify(results, null, 2));
72
+ return;
73
+ }
74
+ if (xmlMode) {
75
+ yield* Console.log(toXml(results));
76
+ return;
77
+ }
78
+ if (results.length === 0) {
79
+ yield* Console.log("No issues in cycle");
80
+ return;
81
+ }
82
+ const lines = results.map((ci) => {
83
+ if (ci.issue_detail) {
84
+ const seq = String(ci.issue_detail.sequence_id).padStart(3, " ");
85
+ return `${key}-${seq} ${ci.issue_detail.name} (${ci.id})`;
86
+ }
87
+ return `${ci.issue} (cycle-issue: ${ci.id})`;
88
+ });
89
+ yield* Console.log(lines.join("\n"));
90
+ });
91
+ }
92
+
54
93
  export const cycleIssuesList = Command.make(
55
94
  "list",
56
95
  { project: projectArg, cycleId: cycleIdArg },
57
- ({ project, cycleId }) =>
58
- Effect.gen(function* () {
59
- const { key, id } = yield* resolveProject(project);
60
- const raw = yield* api.get(
61
- `projects/${id}/cycles/${cycleId}/cycle-issues/`,
62
- );
63
- const { results } = yield* decodeOrFail(CycleIssuesResponseSchema, raw);
64
- if (jsonMode) {
65
- yield* Console.log(JSON.stringify(results, null, 2));
66
- return;
67
- }
68
- if (xmlMode) {
69
- yield* Console.log(toXml(results));
70
- return;
71
- }
72
- if (results.length === 0) {
73
- yield* Console.log("No issues in cycle");
74
- return;
75
- }
76
- const lines = results.map((ci) => {
77
- if (ci.issue_detail) {
78
- const seq = String(ci.issue_detail.sequence_id).padStart(3, " ");
79
- return `${key}-${seq} ${ci.issue_detail.name} (${ci.id})`;
80
- }
81
- return `${ci.issue} (cycle-issue: ${ci.id})`;
82
- });
83
- yield* Console.log(lines.join("\n"));
84
- }),
96
+ cycleIssuesListHandler,
85
97
  ).pipe(
86
98
  Command.withDescription(
87
99
  "List issues in a cycle.\n\nExample:\n plane cycles issues list PROJ <cycle-id>",
@@ -94,19 +106,30 @@ const issueRefArg = Args.text({ name: "ref" }).pipe(
94
106
  Args.withDescription("Issue reference to add (e.g. PROJ-29)"),
95
107
  );
96
108
 
109
+ export function cycleIssuesAddHandler({
110
+ project,
111
+ cycleId,
112
+ ref,
113
+ }: {
114
+ project: string;
115
+ cycleId: string;
116
+ ref: string;
117
+ }) {
118
+ return Effect.gen(function* () {
119
+ const { id: projectId } = yield* resolveProject(project);
120
+ const { seq } = yield* parseIssueRef(ref);
121
+ const issue = yield* findIssueBySeq(projectId, seq);
122
+ yield* api.post(`projects/${projectId}/cycles/${cycleId}/cycle-issues/`, {
123
+ issues: [issue.id],
124
+ });
125
+ yield* Console.log(`Added ${ref} to cycle ${cycleId}`);
126
+ });
127
+ }
128
+
97
129
  export const cycleIssuesAdd = Command.make(
98
130
  "add",
99
131
  { project: projectArg, cycleId: cycleIdArg, ref: issueRefArg },
100
- ({ project, cycleId, ref }) =>
101
- Effect.gen(function* () {
102
- const { id: projectId } = yield* resolveProject(project);
103
- const { seq } = yield* parseIssueRef(ref);
104
- const issue = yield* findIssueBySeq(projectId, seq);
105
- yield* api.post(`projects/${projectId}/cycles/${cycleId}/cycle-issues/`, {
106
- issues: [issue.id],
107
- });
108
- yield* Console.log(`Added ${ref} to cycle ${cycleId}`);
109
- }),
132
+ cycleIssuesAddHandler,
110
133
  ).pipe(
111
134
  Command.withDescription(
112
135
  "Add an issue to a cycle.\n\nExample:\n plane cycles issues add PROJ <cycle-id> PROJ-29",
@@ -20,37 +20,39 @@ const STATUS_LABELS: Record<number, string> = {
20
20
 
21
21
  // --- intake list ---
22
22
 
23
+ export function intakeListHandler({ project }: { project: string }) {
24
+ return Effect.gen(function* () {
25
+ const { id } = yield* resolveProject(project);
26
+ const raw = yield* api.get(`projects/${id}/intake-issues/`);
27
+ const { results } = yield* decodeOrFail(IntakeIssuesResponseSchema, raw);
28
+ if (jsonMode) {
29
+ yield* Console.log(JSON.stringify(results, null, 2));
30
+ return;
31
+ }
32
+ if (xmlMode) {
33
+ yield* Console.log(toXml(results));
34
+ return;
35
+ }
36
+ if (results.length === 0) {
37
+ yield* Console.log("No intake issues");
38
+ return;
39
+ }
40
+ const lines = results.map((i) => {
41
+ const status = STATUS_LABELS[i.status ?? 0] ?? String(i.status ?? "?");
42
+ const statusPad = status.padEnd(10);
43
+ if (i.issue_detail) {
44
+ return `${i.id} [${statusPad}] ${i.issue_detail.name}`;
45
+ }
46
+ return `${i.id} [${statusPad}]`;
47
+ });
48
+ yield* Console.log(lines.join("\n"));
49
+ });
50
+ }
51
+
23
52
  export const intakeList = Command.make(
24
53
  "list",
25
54
  { project: projectArg },
26
- ({ project }) =>
27
- Effect.gen(function* () {
28
- const { id } = yield* resolveProject(project);
29
- const raw = yield* api.get(`projects/${id}/intake-issues/`);
30
- const { results } = yield* decodeOrFail(IntakeIssuesResponseSchema, raw);
31
- if (jsonMode) {
32
- yield* Console.log(JSON.stringify(results, null, 2));
33
- return;
34
- }
35
- if (xmlMode) {
36
- yield* Console.log(toXml(results));
37
- return;
38
- }
39
- if (results.length === 0) {
40
- yield* Console.log("No intake issues");
41
- return;
42
- }
43
- const lines = results.map((i) => {
44
- const status = STATUS_LABELS[i.status ?? 0] ?? String(i.status ?? "?");
45
- const statusPad = status.padEnd(10);
46
- if (i.issue_detail) {
47
- const seq = String(i.issue_detail.sequence_id).padStart(3, " ");
48
- return `${i.id} [${statusPad}] ${i.issue_detail.name}`;
49
- }
50
- return `${i.id} [${statusPad}]`;
51
- });
52
- yield* Console.log(lines.join("\n"));
53
- }),
55
+ intakeListHandler,
54
56
  ).pipe(
55
57
  Command.withDescription(
56
58
  "List intake (triage) issues for a project. Shows status: pending, accepted, rejected, snoozed, duplicate.\n\nExample:\n plane intake list PROJ",
@@ -63,17 +65,26 @@ const intakeIdArg = Args.text({ name: "intake-id" }).pipe(
63
65
  Args.withDescription("Intake issue ID (from 'plane intake list')"),
64
66
  );
65
67
 
68
+ export function intakeAcceptHandler({
69
+ project,
70
+ intakeId,
71
+ }: {
72
+ project: string;
73
+ intakeId: string;
74
+ }) {
75
+ return Effect.gen(function* () {
76
+ const { id } = yield* resolveProject(project);
77
+ yield* api.patch(`projects/${id}/intake-issues/${intakeId}/`, {
78
+ status: 1,
79
+ });
80
+ yield* Console.log(`Intake issue ${intakeId} accepted`);
81
+ });
82
+ }
83
+
66
84
  export const intakeAccept = Command.make(
67
85
  "accept",
68
86
  { project: projectArg, intakeId: intakeIdArg },
69
- ({ project, intakeId }) =>
70
- Effect.gen(function* () {
71
- const { id } = yield* resolveProject(project);
72
- yield* api.patch(`projects/${id}/intake-issues/${intakeId}/`, {
73
- status: 1,
74
- });
75
- yield* Console.log(`Intake issue ${intakeId} accepted`);
76
- }),
87
+ intakeAcceptHandler,
77
88
  ).pipe(
78
89
  Command.withDescription(
79
90
  "Accept an intake issue, creating it as a tracked work item.",
@@ -82,17 +93,26 @@ export const intakeAccept = Command.make(
82
93
 
83
94
  // --- intake reject ---
84
95
 
96
+ export function intakeRejectHandler({
97
+ project,
98
+ intakeId,
99
+ }: {
100
+ project: string;
101
+ intakeId: string;
102
+ }) {
103
+ return Effect.gen(function* () {
104
+ const { id } = yield* resolveProject(project);
105
+ yield* api.patch(`projects/${id}/intake-issues/${intakeId}/`, {
106
+ status: -2,
107
+ });
108
+ yield* Console.log(`Intake issue ${intakeId} rejected`);
109
+ });
110
+ }
111
+
85
112
  export const intakeReject = Command.make(
86
113
  "reject",
87
114
  { project: projectArg, intakeId: intakeIdArg },
88
- ({ project, intakeId }) =>
89
- Effect.gen(function* () {
90
- const { id } = yield* resolveProject(project);
91
- yield* api.patch(`projects/${id}/intake-issues/${intakeId}/`, {
92
- status: -2,
93
- });
94
- yield* Console.log(`Intake issue ${intakeId} rejected`);
95
- }),
115
+ intakeRejectHandler,
96
116
  ).pipe(Command.withDescription("Reject an intake issue."));
97
117
 
98
118
  // --- intake (parent) ---