@forloop-cc/forloop-cli 0.2.3 → 0.2.5
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 +17 -9
- package/dist/forloop +133 -184
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,6 @@ Command-line interface for the [ForLoop](https://forloop.cc) AI development plat
|
|
|
8
8
|
- [Installation](#installation)
|
|
9
9
|
- [Quick Start](#quick-start)
|
|
10
10
|
- [Authentication](#authentication)
|
|
11
|
-
- [Environments](#environments)
|
|
12
11
|
- [Commands](#commands)
|
|
13
12
|
- [Sprint Operations](#sprint-operations)
|
|
14
13
|
- [Story Operations](#story-operations)
|
|
@@ -63,7 +62,7 @@ npm install -g @forloop-cc/forloop-cli
|
|
|
63
62
|
|
|
64
63
|
```bash
|
|
65
64
|
forloop --version
|
|
66
|
-
# Output: forloop 0.2.
|
|
65
|
+
# Output: forloop 0.2.4
|
|
67
66
|
```
|
|
68
67
|
|
|
69
68
|
---
|
|
@@ -129,10 +128,6 @@ forloop auth logout # Remove saved token
|
|
|
129
128
|
|
|
130
129
|
---
|
|
131
130
|
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## Commands
|
|
135
|
-
|
|
136
131
|
### Sprint Operations
|
|
137
132
|
|
|
138
133
|
Work with sprints — the containers that hold your stories and track development cycles.
|
|
@@ -141,11 +136,14 @@ Work with sprints — the containers that hold your stories and track developmen
|
|
|
141
136
|
# List all sprints you have access to
|
|
142
137
|
forloop sprint list
|
|
143
138
|
forloop sprint list --org-id 1
|
|
139
|
+
forloop sprint list --include-system-org # Include system organization sprints
|
|
140
|
+
forloop sprint list --output json # Structured JSON output
|
|
144
141
|
|
|
145
142
|
# Get sprint details, including all stories
|
|
146
143
|
forloop sprint get --id 66
|
|
147
144
|
forloop sprint get --id 66 --no-stories # Exclude stories
|
|
148
145
|
forloop sprint get --id 66 --no-files # Exclude files
|
|
146
|
+
forloop sprint get --id 66 --output json # Structured JSON output
|
|
149
147
|
|
|
150
148
|
# Create a new sprint
|
|
151
149
|
forloop sprint create \
|
|
@@ -193,6 +191,7 @@ forloop story create \
|
|
|
193
191
|
# Read story details, including developer comments
|
|
194
192
|
forloop story get --id 229
|
|
195
193
|
forloop story get --id 229 --no-comments
|
|
194
|
+
forloop story get --id 229 --output json # Structured JSON output
|
|
196
195
|
|
|
197
196
|
# Update a story
|
|
198
197
|
forloop story update --id 229 --status in_progress
|
|
@@ -210,7 +209,11 @@ Templates define the structure of your stories. List available templates to see
|
|
|
210
209
|
|
|
211
210
|
```bash
|
|
212
211
|
forloop template list
|
|
213
|
-
#
|
|
212
|
+
# Built-in templates: Basic Task (basic-task), Basic Note (basic-note)
|
|
213
|
+
# Server templates: Schedule Meeting (schedule-meeting), New Folder (doc-folder)
|
|
214
|
+
|
|
215
|
+
# JSON output:
|
|
216
|
+
forloop template list --output json
|
|
214
217
|
```
|
|
215
218
|
|
|
216
219
|
### Agent Operations
|
|
@@ -220,6 +223,7 @@ Interact with ForLoop's AI agents — check their progress, trigger new work, an
|
|
|
220
223
|
```bash
|
|
221
224
|
# See what AI agents have discussed in the past
|
|
222
225
|
forloop agent history --sprint 66 --limit 20
|
|
226
|
+
forloop agent history --sprint 66 --output json
|
|
223
227
|
|
|
224
228
|
# Check if a developer task is running, completed, or failed
|
|
225
229
|
forloop agent developer-status --sprint 66
|
|
@@ -241,9 +245,11 @@ Organizations group sprints and control access for teams.
|
|
|
241
245
|
# List organizations you belong to
|
|
242
246
|
forloop org list
|
|
243
247
|
forloop org list --owned-only # Only orgs you own
|
|
248
|
+
forloop org list --output json # Structured JSON output
|
|
244
249
|
|
|
245
250
|
# View organization details
|
|
246
251
|
forloop org get --id 1
|
|
252
|
+
forloop org get --id 1 --output json
|
|
247
253
|
|
|
248
254
|
# Create an organization (requires Team or Enterprise tier)
|
|
249
255
|
forloop org create \
|
|
@@ -261,14 +267,15 @@ Check your profile, limits, and usage.
|
|
|
261
267
|
```bash
|
|
262
268
|
# View your profile
|
|
263
269
|
forloop user profile
|
|
264
|
-
|
|
270
|
+
forloop user profile --output json
|
|
265
271
|
|
|
266
272
|
# Check your quota limits and current usage
|
|
267
273
|
forloop user quotas
|
|
268
|
-
|
|
274
|
+
forloop user quotas --output json
|
|
269
275
|
|
|
270
276
|
# Check quota usage for a specific organization
|
|
271
277
|
forloop org quotas --org-id 1
|
|
278
|
+
forloop org quotas --org-id 1 --output json
|
|
272
279
|
```
|
|
273
280
|
|
|
274
281
|
### File Operations
|
|
@@ -282,6 +289,7 @@ forloop file upload --path ./mockup.png --sprint 66 --folder designs
|
|
|
282
289
|
|
|
283
290
|
# List all files in a sprint
|
|
284
291
|
forloop file list --sprint 66
|
|
292
|
+
forloop file list --sprint 66 --output json
|
|
285
293
|
|
|
286
294
|
# Generate a download link
|
|
287
295
|
forloop file download --id 84
|
package/dist/forloop
CHANGED
|
@@ -912,7 +912,6 @@ function createAuthStatusCommand() {
|
|
|
912
912
|
} else {
|
|
913
913
|
console.log("Not authenticated");
|
|
914
914
|
console.log("Run: forloop auth login --api-key <token>");
|
|
915
|
-
process.exit(1);
|
|
916
915
|
}
|
|
917
916
|
}
|
|
918
917
|
});
|
|
@@ -929,6 +928,25 @@ function createAuthLogoutCommand() {
|
|
|
929
928
|
});
|
|
930
929
|
}
|
|
931
930
|
|
|
931
|
+
// src/output/formatter.ts
|
|
932
|
+
function print(config, data, label) {
|
|
933
|
+
if (config.output === "json") {
|
|
934
|
+
console.log(JSON.stringify(data, null, 2));
|
|
935
|
+
} else if (typeof data === "string") {
|
|
936
|
+
if (label && !config.quiet) console.log(label);
|
|
937
|
+
console.log(data);
|
|
938
|
+
} else {
|
|
939
|
+
if (label && !config.quiet) console.log(label);
|
|
940
|
+
console.log(JSON.stringify(data, null, 2));
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function formatError(message, format) {
|
|
944
|
+
if (format === "json") {
|
|
945
|
+
return JSON.stringify({ error: { message } }, null, 2);
|
|
946
|
+
}
|
|
947
|
+
return `Error: ${message}`;
|
|
948
|
+
}
|
|
949
|
+
|
|
932
950
|
// src/commands/story.ts
|
|
933
951
|
function createStoryCommands() {
|
|
934
952
|
return {
|
|
@@ -945,7 +963,7 @@ async function getClient() {
|
|
|
945
963
|
console.error("Error: No API token configured. Run: forloop auth login --api-key <token>");
|
|
946
964
|
process.exit(1);
|
|
947
965
|
}
|
|
948
|
-
const client = new ForLoopAPIClient({ token, baseUrl: config.apiUrl });
|
|
966
|
+
const client = new ForLoopAPIClient({ token, baseUrl: config.apiUrl, timeout: 3e4 });
|
|
949
967
|
return { client };
|
|
950
968
|
}
|
|
951
969
|
function createStoryCreateCommand() {
|
|
@@ -967,7 +985,7 @@ function createStoryCreateCommand() {
|
|
|
967
985
|
'forloop story create --title "Meeting notes" --sprint 1 --type basic-note',
|
|
968
986
|
'forloop story create --title "Design Docs" --sprint 1'
|
|
969
987
|
],
|
|
970
|
-
async run(
|
|
988
|
+
async run(config, flags) {
|
|
971
989
|
const resolved = await getClient();
|
|
972
990
|
if (!resolved) return;
|
|
973
991
|
const { client } = resolved;
|
|
@@ -1007,7 +1025,7 @@ function createStoryCreateCommand() {
|
|
|
1007
1025
|
points: flags.points
|
|
1008
1026
|
})
|
|
1009
1027
|
});
|
|
1010
|
-
|
|
1028
|
+
print(config, story, `Story created: #${story.id} - ${story.title} (${story.type})`);
|
|
1011
1029
|
} else {
|
|
1012
1030
|
const story = await client.createStory({
|
|
1013
1031
|
title: flags.title,
|
|
@@ -1017,7 +1035,7 @@ function createStoryCreateCommand() {
|
|
|
1017
1035
|
priority: flags.priority || "medium",
|
|
1018
1036
|
status: "todo"
|
|
1019
1037
|
});
|
|
1020
|
-
|
|
1038
|
+
print(config, story, `Doc folder created: #${story.id} - ${story.title}`);
|
|
1021
1039
|
}
|
|
1022
1040
|
}
|
|
1023
1041
|
});
|
|
@@ -1032,7 +1050,7 @@ function createStoryGetCommand() {
|
|
|
1032
1050
|
{ flag: "--no-comments", description: "Exclude comments" }
|
|
1033
1051
|
],
|
|
1034
1052
|
examples: ["forloop story get --id 123"],
|
|
1035
|
-
async run(
|
|
1053
|
+
async run(config, flags) {
|
|
1036
1054
|
const resolved = await getClient();
|
|
1037
1055
|
if (!resolved) return;
|
|
1038
1056
|
const { client } = resolved;
|
|
@@ -1042,28 +1060,7 @@ function createStoryGetCommand() {
|
|
|
1042
1060
|
process.exit(1);
|
|
1043
1061
|
}
|
|
1044
1062
|
const story = await client.getStory(storyId, { includeComments: !flags.noComments });
|
|
1045
|
-
|
|
1046
|
-
`Story #${story.id}: ${story.title}`,
|
|
1047
|
-
`Sprint: #${story.sprint?.id || "N/A"} ${story.sprint?.title || ""}`,
|
|
1048
|
-
`Status: ${story.status}`,
|
|
1049
|
-
`Priority: ${story.priority}`,
|
|
1050
|
-
`Type: ${story.type}`
|
|
1051
|
-
];
|
|
1052
|
-
if (story.points) lines.push(`Points: ${story.points}`);
|
|
1053
|
-
if (story.assignee) lines.push(`Assignee: ${story.assignee.name || story.assignee.email || "Unknown"}`);
|
|
1054
|
-
if (story.assigneeAgentKey) lines.push(`Agent: ${story.assigneeAgentKey}`);
|
|
1055
|
-
if (story.description) {
|
|
1056
|
-
lines.push("", "Description:", story.description);
|
|
1057
|
-
}
|
|
1058
|
-
if (!flags.noComments && story.comments?.length) {
|
|
1059
|
-
lines.push("", `--- Comments (${story.comments.length}) ---`);
|
|
1060
|
-
for (const comment of story.comments) {
|
|
1061
|
-
const author = comment.authorAgentKey || comment.user?.name || "Unknown";
|
|
1062
|
-
lines.push(` ${author}: ${comment.content || comment.body || ""}`);
|
|
1063
|
-
if (comment.artifacts?.length) lines.push(` Artifacts: ${comment.artifacts.join(", ")}`);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
console.log(lines.join("\n"));
|
|
1063
|
+
print(config, story);
|
|
1067
1064
|
}
|
|
1068
1065
|
});
|
|
1069
1066
|
}
|
|
@@ -1081,7 +1078,7 @@ function createStoryUpdateCommand() {
|
|
|
1081
1078
|
{ flag: "--points <n>", description: "Story points (0-10)", type: "number" }
|
|
1082
1079
|
],
|
|
1083
1080
|
examples: ["forloop story update --id 123 --status in_progress"],
|
|
1084
|
-
async run(
|
|
1081
|
+
async run(config, flags) {
|
|
1085
1082
|
const resolved = await getClient();
|
|
1086
1083
|
if (!resolved) return;
|
|
1087
1084
|
const { client } = resolved;
|
|
@@ -1101,7 +1098,7 @@ function createStoryUpdateCommand() {
|
|
|
1101
1098
|
process.exit(1);
|
|
1102
1099
|
}
|
|
1103
1100
|
const story = await client.updateStory(storyId, data);
|
|
1104
|
-
|
|
1101
|
+
print(config, story, `Story #${story.id} updated successfully.`);
|
|
1105
1102
|
}
|
|
1106
1103
|
});
|
|
1107
1104
|
}
|
|
@@ -1115,7 +1112,7 @@ function createStoryDeleteCommand() {
|
|
|
1115
1112
|
{ flag: "--confirm", description: "Confirm deletion" }
|
|
1116
1113
|
],
|
|
1117
1114
|
examples: ["forloop story delete --id 123 --confirm"],
|
|
1118
|
-
async run(
|
|
1115
|
+
async run(config, flags) {
|
|
1119
1116
|
if (!flags.confirm) {
|
|
1120
1117
|
console.error("Error: --confirm is required to permanently delete a story.");
|
|
1121
1118
|
process.exit(1);
|
|
@@ -1124,7 +1121,7 @@ function createStoryDeleteCommand() {
|
|
|
1124
1121
|
if (!resolved) return;
|
|
1125
1122
|
const { client } = resolved;
|
|
1126
1123
|
await client.deleteStory(flags.id);
|
|
1127
|
-
|
|
1124
|
+
print(config, { deleted: flags.id }, `Story #${flags.id} deleted successfully.`);
|
|
1128
1125
|
}
|
|
1129
1126
|
});
|
|
1130
1127
|
}
|
|
@@ -1146,12 +1143,9 @@ async function getClient2() {
|
|
|
1146
1143
|
console.error("Error: No API token configured. Run: forloop auth login --api-key <token>");
|
|
1147
1144
|
process.exit(1);
|
|
1148
1145
|
}
|
|
1149
|
-
const client = new ForLoopAPIClient({ token, baseUrl: config.apiUrl });
|
|
1146
|
+
const client = new ForLoopAPIClient({ token, baseUrl: config.apiUrl, timeout: 3e4 });
|
|
1150
1147
|
return { client };
|
|
1151
1148
|
}
|
|
1152
|
-
function formatDate(dateString) {
|
|
1153
|
-
return new Date(dateString).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
|
|
1154
|
-
}
|
|
1155
1149
|
function createSprintGetCommand() {
|
|
1156
1150
|
return defineCommand({
|
|
1157
1151
|
name: "sprint get",
|
|
@@ -1163,7 +1157,7 @@ function createSprintGetCommand() {
|
|
|
1163
1157
|
{ flag: "--no-files", description: "Exclude files from response" }
|
|
1164
1158
|
],
|
|
1165
1159
|
examples: ["forloop sprint get --id 456", "forloop sprint get --id 456 --no-files"],
|
|
1166
|
-
async run(
|
|
1160
|
+
async run(config, flags) {
|
|
1167
1161
|
const resolved = await getClient2();
|
|
1168
1162
|
if (!resolved) return;
|
|
1169
1163
|
const { client } = resolved;
|
|
@@ -1176,20 +1170,7 @@ function createSprintGetCommand() {
|
|
|
1176
1170
|
includeStories: !flags.noStories,
|
|
1177
1171
|
includeFiles: !flags.noFiles
|
|
1178
1172
|
});
|
|
1179
|
-
|
|
1180
|
-
`Sprint #${sprint.id}: ${sprint.title}`,
|
|
1181
|
-
`Status: ${sprint.status}`,
|
|
1182
|
-
`Period: ${formatDate(sprint.startDate)} - ${formatDate(sprint.endDate)}`,
|
|
1183
|
-
`Stories: ${sprint.stories?.length || 0}`
|
|
1184
|
-
];
|
|
1185
|
-
if (sprint.stories?.length) {
|
|
1186
|
-
lines.push("", "Stories:", "");
|
|
1187
|
-
for (const story of sprint.stories) {
|
|
1188
|
-
const icons = { todo: "[ ]", in_progress: "[>]", done: "[x]", blocked: "[!]" };
|
|
1189
|
-
lines.push(` ${icons[story.status] || "[?]"} #${story.id} ${story.title} (${story.status})`);
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
console.log(lines.join("\n"));
|
|
1173
|
+
print(config, sprint);
|
|
1193
1174
|
}
|
|
1194
1175
|
});
|
|
1195
1176
|
}
|
|
@@ -1203,23 +1184,15 @@ function createSprintListCommand() {
|
|
|
1203
1184
|
{ flag: "--include-system-org", description: "Include system organization sprints" }
|
|
1204
1185
|
],
|
|
1205
1186
|
examples: ["forloop sprint list", "forloop sprint list --org-id 1"],
|
|
1206
|
-
async run(
|
|
1187
|
+
async run(config, flags) {
|
|
1207
1188
|
const resolved = await getClient2();
|
|
1208
1189
|
if (!resolved) return;
|
|
1209
1190
|
const { client } = resolved;
|
|
1210
1191
|
const params = {};
|
|
1211
1192
|
if (flags.orgId) params.organizationId = String(flags.orgId);
|
|
1193
|
+
if (flags.includeSystemOrg) params.includeSystemOrg = "true";
|
|
1212
1194
|
const sprints = await client.listSprints(params);
|
|
1213
|
-
|
|
1214
|
-
console.log("No sprints found.");
|
|
1215
|
-
return;
|
|
1216
|
-
}
|
|
1217
|
-
const lines = ["Sprints:", ""];
|
|
1218
|
-
for (const sprint of sprints) {
|
|
1219
|
-
const orgInfo = sprint.organizationName ? ` (${sprint.organizationName})` : "";
|
|
1220
|
-
lines.push(` #${sprint.id} ${sprint.title}${orgInfo} - ${sprint.status}`);
|
|
1221
|
-
}
|
|
1222
|
-
console.log(lines.join("\n"));
|
|
1195
|
+
print(config, sprints);
|
|
1223
1196
|
}
|
|
1224
1197
|
});
|
|
1225
1198
|
}
|
|
@@ -1237,7 +1210,7 @@ function createSprintCreateCommand() {
|
|
|
1237
1210
|
{ flag: "--org-id <id>", description: "Organization ID", type: "number" }
|
|
1238
1211
|
],
|
|
1239
1212
|
examples: ['forloop sprint create --title "Sprint 15" --start-date 2026-06-09 --end-date 2026-06-23'],
|
|
1240
|
-
async run(
|
|
1213
|
+
async run(config, flags) {
|
|
1241
1214
|
const resolved = await getClient2();
|
|
1242
1215
|
if (!resolved) return;
|
|
1243
1216
|
const { client } = resolved;
|
|
@@ -1253,8 +1226,7 @@ function createSprintCreateCommand() {
|
|
|
1253
1226
|
isPrivate: flags.private === true,
|
|
1254
1227
|
organizationId: flags.orgId
|
|
1255
1228
|
});
|
|
1256
|
-
|
|
1257
|
-
console.log(`Period: ${formatDate(sprint.startDate)} - ${formatDate(sprint.endDate)}`);
|
|
1229
|
+
print(config, sprint, `Sprint created: #${sprint.id} - ${sprint.title}`);
|
|
1258
1230
|
}
|
|
1259
1231
|
});
|
|
1260
1232
|
}
|
|
@@ -1272,7 +1244,7 @@ function createSprintUpdateCommand() {
|
|
|
1272
1244
|
{ flag: "--private", description: "Set visibility to private" }
|
|
1273
1245
|
],
|
|
1274
1246
|
examples: ['forloop sprint update --id 456 --title "Updated Sprint"'],
|
|
1275
|
-
async run(
|
|
1247
|
+
async run(config, flags) {
|
|
1276
1248
|
const resolved = await getClient2();
|
|
1277
1249
|
if (!resolved) return;
|
|
1278
1250
|
const { client } = resolved;
|
|
@@ -1287,7 +1259,7 @@ function createSprintUpdateCommand() {
|
|
|
1287
1259
|
process.exit(1);
|
|
1288
1260
|
}
|
|
1289
1261
|
const sprint = await client.updateSprint(flags.id, updateData);
|
|
1290
|
-
|
|
1262
|
+
print(config, sprint, `Sprint #${sprint.id} updated successfully.`);
|
|
1291
1263
|
}
|
|
1292
1264
|
});
|
|
1293
1265
|
}
|
|
@@ -1301,7 +1273,7 @@ function createSprintDeleteCommand() {
|
|
|
1301
1273
|
{ flag: "--confirm", description: "Confirm deletion" }
|
|
1302
1274
|
],
|
|
1303
1275
|
examples: ["forloop sprint delete --id 456 --confirm"],
|
|
1304
|
-
async run(
|
|
1276
|
+
async run(config, flags) {
|
|
1305
1277
|
if (!flags.confirm) {
|
|
1306
1278
|
console.error("Error: --confirm is required to permanently delete a sprint.");
|
|
1307
1279
|
process.exit(1);
|
|
@@ -1309,21 +1281,13 @@ function createSprintDeleteCommand() {
|
|
|
1309
1281
|
const resolved = await getClient2();
|
|
1310
1282
|
if (!resolved) return;
|
|
1311
1283
|
const { client } = resolved;
|
|
1312
|
-
await client.deleteSprint(flags.id);
|
|
1313
|
-
|
|
1284
|
+
const result = await client.deleteSprint(flags.id);
|
|
1285
|
+
print(config, result, `Sprint #${flags.id} deleted successfully.`);
|
|
1314
1286
|
}
|
|
1315
1287
|
});
|
|
1316
1288
|
}
|
|
1317
1289
|
|
|
1318
1290
|
// src/commands/org.ts
|
|
1319
|
-
function createOrgCommands() {
|
|
1320
|
-
return {
|
|
1321
|
-
"org list": createOrgListCommand(),
|
|
1322
|
-
"org get": createOrgGetCommand(),
|
|
1323
|
-
"org create": createOrgCreateCommand(),
|
|
1324
|
-
"org update": createOrgUpdateCommand()
|
|
1325
|
-
};
|
|
1326
|
-
}
|
|
1327
1291
|
async function getClient3() {
|
|
1328
1292
|
const config = getConfig();
|
|
1329
1293
|
const token = await getToken();
|
|
@@ -1331,7 +1295,15 @@ async function getClient3() {
|
|
|
1331
1295
|
console.error("Error: No API token configured. Run: forloop auth login --api-key <token>");
|
|
1332
1296
|
process.exit(1);
|
|
1333
1297
|
}
|
|
1334
|
-
return { client: new ForLoopAPIClient({ token, baseUrl: config.apiUrl }) };
|
|
1298
|
+
return { client: new ForLoopAPIClient({ token, baseUrl: config.apiUrl, timeout: 3e4 }) };
|
|
1299
|
+
}
|
|
1300
|
+
function createOrgCommands() {
|
|
1301
|
+
return {
|
|
1302
|
+
"org list": createOrgListCommand(),
|
|
1303
|
+
"org get": createOrgGetCommand(),
|
|
1304
|
+
"org create": createOrgCreateCommand(),
|
|
1305
|
+
"org update": createOrgUpdateCommand()
|
|
1306
|
+
};
|
|
1335
1307
|
}
|
|
1336
1308
|
function createOrgListCommand() {
|
|
1337
1309
|
return defineCommand({
|
|
@@ -1341,12 +1313,16 @@ function createOrgListCommand() {
|
|
|
1341
1313
|
options: [
|
|
1342
1314
|
{ flag: "--owned-only", description: "List only organizations you own" }
|
|
1343
1315
|
],
|
|
1344
|
-
examples: ["forloop org list", "forloop org list --owned-only"],
|
|
1345
|
-
async run(
|
|
1316
|
+
examples: ["forloop org list", "forloop org list --owned-only", "forloop org list --output json"],
|
|
1317
|
+
async run(config, flags) {
|
|
1346
1318
|
const resolved = await getClient3();
|
|
1347
1319
|
if (!resolved) return;
|
|
1348
1320
|
const { client } = resolved;
|
|
1349
1321
|
const orgs = flags.ownedOnly ? await client.getOwnedOrganizations() : await client.listOrganizations();
|
|
1322
|
+
if (config.output === "json") {
|
|
1323
|
+
print(config, orgs);
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1350
1326
|
if (!orgs.length) {
|
|
1351
1327
|
console.log("No organizations found.");
|
|
1352
1328
|
return;
|
|
@@ -1368,19 +1344,16 @@ function createOrgGetCommand() {
|
|
|
1368
1344
|
options: [
|
|
1369
1345
|
{ flag: "--id <number>", description: "Organization ID", required: true, type: "number" }
|
|
1370
1346
|
],
|
|
1371
|
-
examples: ["forloop org get --id 1"],
|
|
1372
|
-
async run(
|
|
1347
|
+
examples: ["forloop org get --id 1", "forloop org get --id 1 --output json"],
|
|
1348
|
+
async run(config, flags) {
|
|
1373
1349
|
if (!flags.id) {
|
|
1374
1350
|
console.error("Error: --id is required");
|
|
1375
1351
|
process.exit(1);
|
|
1376
1352
|
}
|
|
1377
1353
|
const resolved = await getClient3();
|
|
1378
1354
|
if (!resolved) return;
|
|
1379
|
-
const
|
|
1380
|
-
|
|
1381
|
-
console.log(`Organization #${org.id}: ${org.name}`);
|
|
1382
|
-
console.log(`Type: ${org.type || "user"}`);
|
|
1383
|
-
if (org.description) console.log(`Description: ${org.description}`);
|
|
1355
|
+
const org = await resolved.client.getOrganization(flags.id);
|
|
1356
|
+
print(config, org);
|
|
1384
1357
|
}
|
|
1385
1358
|
});
|
|
1386
1359
|
}
|
|
@@ -1394,19 +1367,18 @@ function createOrgCreateCommand() {
|
|
|
1394
1367
|
{ flag: "--description <text>", description: "Organization description" }
|
|
1395
1368
|
],
|
|
1396
1369
|
examples: ['forloop org create --name "My Team" --description "Engineering team"'],
|
|
1397
|
-
async run(
|
|
1370
|
+
async run(config, flags) {
|
|
1398
1371
|
if (!flags.name) {
|
|
1399
1372
|
console.error("Error: --name is required");
|
|
1400
1373
|
process.exit(1);
|
|
1401
1374
|
}
|
|
1402
1375
|
const resolved = await getClient3();
|
|
1403
1376
|
if (!resolved) return;
|
|
1404
|
-
const
|
|
1405
|
-
const org = await client.createOrganization({
|
|
1377
|
+
const org = await resolved.client.createOrganization({
|
|
1406
1378
|
name: flags.name,
|
|
1407
1379
|
description: flags.description
|
|
1408
1380
|
});
|
|
1409
|
-
|
|
1381
|
+
print(config, org, `Organization created: #${org.id} - ${org.name}`);
|
|
1410
1382
|
}
|
|
1411
1383
|
});
|
|
1412
1384
|
}
|
|
@@ -1421,19 +1393,18 @@ function createOrgUpdateCommand() {
|
|
|
1421
1393
|
{ flag: "--description <text>", description: "New description" }
|
|
1422
1394
|
],
|
|
1423
1395
|
examples: ['forloop org update --id 1 --name "New Name"'],
|
|
1424
|
-
async run(
|
|
1396
|
+
async run(config, flags) {
|
|
1425
1397
|
if (!flags.id) {
|
|
1426
1398
|
console.error("Error: --id is required");
|
|
1427
1399
|
process.exit(1);
|
|
1428
1400
|
}
|
|
1429
1401
|
const resolved = await getClient3();
|
|
1430
1402
|
if (!resolved) return;
|
|
1431
|
-
const
|
|
1432
|
-
const org = await client.updateOrganization(flags.id, {
|
|
1403
|
+
const org = await resolved.client.updateOrganization(flags.id, {
|
|
1433
1404
|
name: flags.name,
|
|
1434
1405
|
description: flags.description
|
|
1435
1406
|
});
|
|
1436
|
-
|
|
1407
|
+
print(config, org, `Organization #${org.id} updated successfully.`);
|
|
1437
1408
|
}
|
|
1438
1409
|
});
|
|
1439
1410
|
}
|
|
@@ -1447,30 +1418,41 @@ function createTemplateCommands() {
|
|
|
1447
1418
|
function createTemplateListCommand() {
|
|
1448
1419
|
return defineCommand({
|
|
1449
1420
|
name: "template list",
|
|
1450
|
-
description: "List available story templates",
|
|
1421
|
+
description: "List available story templates (basic-task, basic-note, schedule-meeting, doc-folder)",
|
|
1451
1422
|
usage: "forloop template list",
|
|
1452
|
-
examples: ["forloop template list"],
|
|
1453
|
-
async run(
|
|
1454
|
-
const
|
|
1423
|
+
examples: ["forloop template list", "forloop template list --output json"],
|
|
1424
|
+
async run(config) {
|
|
1425
|
+
const apiConfig = getConfig();
|
|
1455
1426
|
const token = await getToken();
|
|
1456
1427
|
if (!token) {
|
|
1457
1428
|
console.error("Error: No API token configured.");
|
|
1458
1429
|
process.exit(1);
|
|
1459
1430
|
}
|
|
1460
|
-
const client = new ForLoopAPIClient({ token, baseUrl:
|
|
1431
|
+
const client = new ForLoopAPIClient({ token, baseUrl: apiConfig.apiUrl, timeout: 3e4 });
|
|
1461
1432
|
const templates = await client.listTemplates();
|
|
1462
1433
|
const active = templates.filter((t) => !t.deletedAt);
|
|
1434
|
+
if (config.output === "json") {
|
|
1435
|
+
print(config, active);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1463
1438
|
if (!active.length) {
|
|
1464
1439
|
console.log("No templates found.");
|
|
1465
1440
|
return;
|
|
1466
1441
|
}
|
|
1442
|
+
const builtIn = [
|
|
1443
|
+
{ name: "Basic Task", slug: "basic-task", description: "Standard task with title, description, priority, and story points", fields: [] },
|
|
1444
|
+
{ name: "Basic Note", slug: "basic-note", description: "Simple note with title and description", fields: [] }
|
|
1445
|
+
];
|
|
1446
|
+
const allTemplates = [...builtIn, ...active];
|
|
1467
1447
|
const lines = ["Available Templates:", ""];
|
|
1468
|
-
for (const t of
|
|
1469
|
-
const fields = JSON.parse(t.fields);
|
|
1470
|
-
const fieldNames = fields.map((f) => f.label).join(", ");
|
|
1471
|
-
|
|
1448
|
+
for (const t of allTemplates) {
|
|
1449
|
+
const fields = t.fields && typeof t.fields === "string" ? JSON.parse(t.fields) : Array.isArray(t.fields) ? t.fields : [];
|
|
1450
|
+
const fieldNames = Array.isArray(fields) ? fields.map((f) => f.label || f).join(", ") : "";
|
|
1451
|
+
const isBuiltIn = builtIn.some((b) => b.slug === t.slug);
|
|
1452
|
+
const badge = isBuiltIn ? " (built-in)" : "";
|
|
1453
|
+
lines.push(` ${t.name} (slug: ${t.slug})${badge}`);
|
|
1472
1454
|
lines.push(` ${t.description}`);
|
|
1473
|
-
lines.push(` Fields: ${fieldNames}`);
|
|
1455
|
+
if (fieldNames) lines.push(` Fields: ${fieldNames}`);
|
|
1474
1456
|
lines.push("");
|
|
1475
1457
|
}
|
|
1476
1458
|
console.log(lines.join("\n"));
|
|
@@ -1493,7 +1475,7 @@ async function getClient4() {
|
|
|
1493
1475
|
console.error("Error: No API token configured.");
|
|
1494
1476
|
process.exit(1);
|
|
1495
1477
|
}
|
|
1496
|
-
return { client: new ForLoopAPIClient({ token, baseUrl: config.apiUrl }) };
|
|
1478
|
+
return { client: new ForLoopAPIClient({ token, baseUrl: config.apiUrl, timeout: 3e4 }) };
|
|
1497
1479
|
}
|
|
1498
1480
|
function createAgentHistoryCommand() {
|
|
1499
1481
|
return defineCommand({
|
|
@@ -1505,7 +1487,7 @@ function createAgentHistoryCommand() {
|
|
|
1505
1487
|
{ flag: "--limit <n>", description: "Number of messages (max 200, default 50)", type: "number" }
|
|
1506
1488
|
],
|
|
1507
1489
|
examples: ["forloop agent history --sprint 1 --limit 20"],
|
|
1508
|
-
async run(
|
|
1490
|
+
async run(config, flags) {
|
|
1509
1491
|
const resolved = await getClient4();
|
|
1510
1492
|
if (!resolved) return;
|
|
1511
1493
|
const { client } = resolved;
|
|
@@ -1515,21 +1497,7 @@ function createAgentHistoryCommand() {
|
|
|
1515
1497
|
sprintId,
|
|
1516
1498
|
limit: flags.limit || 50
|
|
1517
1499
|
});
|
|
1518
|
-
|
|
1519
|
-
if (!messages.length) {
|
|
1520
|
-
console.log("No conversation history found.");
|
|
1521
|
-
return;
|
|
1522
|
-
}
|
|
1523
|
-
console.log(`Conversation History (${history.total || messages.length} total):`);
|
|
1524
|
-
for (const msg of messages) {
|
|
1525
|
-
const preview = (msg.userMessage || "").substring(0, 80);
|
|
1526
|
-
console.log(` ${preview}${preview.length >= 80 ? "..." : ""}`);
|
|
1527
|
-
if (msg.agentResponse) {
|
|
1528
|
-
const resp = msg.agentResponse.substring(0, 100);
|
|
1529
|
-
console.log(` -> ${resp}${msg.agentResponse.length > 100 ? "..." : ""}`);
|
|
1530
|
-
}
|
|
1531
|
-
console.log("");
|
|
1532
|
-
}
|
|
1500
|
+
print(config, history);
|
|
1533
1501
|
}
|
|
1534
1502
|
});
|
|
1535
1503
|
}
|
|
@@ -1605,13 +1573,6 @@ function createDeveloperStatusCommand() {
|
|
|
1605
1573
|
}
|
|
1606
1574
|
|
|
1607
1575
|
// src/commands/user.ts
|
|
1608
|
-
function createUserCommands() {
|
|
1609
|
-
return {
|
|
1610
|
-
"user profile": createUserProfileCommand(),
|
|
1611
|
-
"user quotas": createUserQuotasCommand(),
|
|
1612
|
-
"org quotas": createOrgQuotasCommand()
|
|
1613
|
-
};
|
|
1614
|
-
}
|
|
1615
1576
|
async function getClient5() {
|
|
1616
1577
|
const config = getConfig();
|
|
1617
1578
|
const token = await getToken();
|
|
@@ -1619,22 +1580,26 @@ async function getClient5() {
|
|
|
1619
1580
|
console.error("Error: No API token configured.");
|
|
1620
1581
|
process.exit(1);
|
|
1621
1582
|
}
|
|
1622
|
-
return { client: new ForLoopAPIClient({ token, baseUrl: config.apiUrl }) };
|
|
1583
|
+
return { client: new ForLoopAPIClient({ token, baseUrl: config.apiUrl, timeout: 3e4 }) };
|
|
1584
|
+
}
|
|
1585
|
+
function createUserCommands() {
|
|
1586
|
+
return {
|
|
1587
|
+
"user profile": createUserProfileCommand(),
|
|
1588
|
+
"user quotas": createUserQuotasCommand(),
|
|
1589
|
+
"org quotas": createOrgQuotasCommand()
|
|
1590
|
+
};
|
|
1623
1591
|
}
|
|
1624
1592
|
function createUserProfileCommand() {
|
|
1625
1593
|
return defineCommand({
|
|
1626
1594
|
name: "user profile",
|
|
1627
1595
|
description: "Get current user profile",
|
|
1628
1596
|
usage: "forloop user profile",
|
|
1629
|
-
examples: ["forloop user profile"],
|
|
1630
|
-
async run(
|
|
1597
|
+
examples: ["forloop user profile", "forloop user profile --output json"],
|
|
1598
|
+
async run(config) {
|
|
1631
1599
|
const resolved = await getClient5();
|
|
1632
1600
|
if (!resolved) return;
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
console.log(`User: ${profile.name || profile.email || "Unknown"}`);
|
|
1636
|
-
console.log(`Email: ${profile.email}`);
|
|
1637
|
-
if (profile.tier) console.log(`Tier: ${profile.tier}`);
|
|
1601
|
+
const profile = await resolved.client.getUserProfile();
|
|
1602
|
+
print(config, profile, "User Profile:");
|
|
1638
1603
|
}
|
|
1639
1604
|
});
|
|
1640
1605
|
}
|
|
@@ -1643,14 +1608,12 @@ function createUserQuotasCommand() {
|
|
|
1643
1608
|
name: "user quotas",
|
|
1644
1609
|
description: "Check user quota limits",
|
|
1645
1610
|
usage: "forloop user quotas",
|
|
1646
|
-
examples: ["forloop user quotas"],
|
|
1647
|
-
async run(
|
|
1611
|
+
examples: ["forloop user quotas", "forloop user quotas --output json"],
|
|
1612
|
+
async run(config) {
|
|
1648
1613
|
const resolved = await getClient5();
|
|
1649
1614
|
if (!resolved) return;
|
|
1650
|
-
const
|
|
1651
|
-
|
|
1652
|
-
console.log("User Quotas:");
|
|
1653
|
-
console.log(JSON.stringify(quotas, null, 2));
|
|
1615
|
+
const quotas = await resolved.client.getUserQuotas();
|
|
1616
|
+
print(config, quotas, "User Quotas:");
|
|
1654
1617
|
}
|
|
1655
1618
|
});
|
|
1656
1619
|
}
|
|
@@ -1662,18 +1625,16 @@ function createOrgQuotasCommand() {
|
|
|
1662
1625
|
options: [
|
|
1663
1626
|
{ flag: "--org-id <id>", description: "Organization ID", required: true, type: "number" }
|
|
1664
1627
|
],
|
|
1665
|
-
examples: ["forloop org quotas --org-id 1"],
|
|
1666
|
-
async run(
|
|
1628
|
+
examples: ["forloop org quotas --org-id 1", "forloop org quotas --org-id 1 --output json"],
|
|
1629
|
+
async run(config, flags) {
|
|
1667
1630
|
if (!flags.orgId) {
|
|
1668
1631
|
console.error("Error: --org-id is required");
|
|
1669
1632
|
process.exit(1);
|
|
1670
1633
|
}
|
|
1671
1634
|
const resolved = await getClient5();
|
|
1672
1635
|
if (!resolved) return;
|
|
1673
|
-
const
|
|
1674
|
-
|
|
1675
|
-
console.log("Organization Quotas:");
|
|
1676
|
-
console.log(JSON.stringify(quotas, null, 2));
|
|
1636
|
+
const quotas = await resolved.client.getOrganizationQuotas(flags.orgId);
|
|
1637
|
+
print(config, quotas, "Organization Quotas:");
|
|
1677
1638
|
}
|
|
1678
1639
|
});
|
|
1679
1640
|
}
|
|
@@ -1696,7 +1657,7 @@ async function getClient6() {
|
|
|
1696
1657
|
console.error("Error: No API token configured.");
|
|
1697
1658
|
process.exit(1);
|
|
1698
1659
|
}
|
|
1699
|
-
return { client: new ForLoopAPIClient({ token, baseUrl: config.apiUrl }) };
|
|
1660
|
+
return { client: new ForLoopAPIClient({ token, baseUrl: config.apiUrl, timeout: 3e4 }) };
|
|
1700
1661
|
}
|
|
1701
1662
|
var mimeTypes = {
|
|
1702
1663
|
".png": "image/png",
|
|
@@ -1727,10 +1688,11 @@ function createFileUploadCommand() {
|
|
|
1727
1688
|
{ flag: "--path <path>", description: "Local file path", required: true },
|
|
1728
1689
|
{ flag: "--sprint <id>", description: "Target sprint ID", required: true, type: "number" },
|
|
1729
1690
|
{ flag: "--description <text>", description: "File description" },
|
|
1730
|
-
{ flag: "--folder <text>", description: "S3 folder within sprint" }
|
|
1691
|
+
{ flag: "--folder <text>", description: "S3 folder within sprint" },
|
|
1692
|
+
{ flag: "--story-id <id>", description: "Link file to a story (e.g., doc_folder story)", type: "number" }
|
|
1731
1693
|
],
|
|
1732
1694
|
examples: ["forloop file upload --path ./report.pdf --sprint 1 --folder docs"],
|
|
1733
|
-
async run(
|
|
1695
|
+
async run(config, flags) {
|
|
1734
1696
|
const resolved = await getClient6();
|
|
1735
1697
|
if (!resolved) return;
|
|
1736
1698
|
const { client } = resolved;
|
|
@@ -1764,10 +1726,12 @@ function createFileUploadCommand() {
|
|
|
1764
1726
|
originalName: fileName,
|
|
1765
1727
|
fileType: contentType,
|
|
1766
1728
|
size: fileContent.length,
|
|
1767
|
-
folder: flags.folder
|
|
1729
|
+
folder: flags.folder,
|
|
1730
|
+
x: 0,
|
|
1731
|
+
y: 0,
|
|
1732
|
+
storyId: flags.storyId
|
|
1768
1733
|
});
|
|
1769
|
-
|
|
1770
|
-
console.log(`URL: ${complete.url}`);
|
|
1734
|
+
print(config, { fileName, size: fileContent.length, url: complete.url, fileId: complete.id }, `File uploaded: ${fileName} (${formatSize(fileContent.length)})`);
|
|
1771
1735
|
}
|
|
1772
1736
|
});
|
|
1773
1737
|
}
|
|
@@ -1780,23 +1744,12 @@ function createFileListCommand() {
|
|
|
1780
1744
|
{ flag: "--sprint <id>", description: "Sprint ID", required: true, type: "number" }
|
|
1781
1745
|
],
|
|
1782
1746
|
examples: ["forloop file list --sprint 1"],
|
|
1783
|
-
async run(
|
|
1747
|
+
async run(config, flags) {
|
|
1784
1748
|
const resolved = await getClient6();
|
|
1785
1749
|
if (!resolved) return;
|
|
1786
1750
|
const { client } = resolved;
|
|
1787
1751
|
const files = await client.getSprintFiles(flags.sprint);
|
|
1788
|
-
|
|
1789
|
-
console.log("No files found.");
|
|
1790
|
-
return;
|
|
1791
|
-
}
|
|
1792
|
-
const lines = [`Files in Sprint #${flags.sprint}:`, ""];
|
|
1793
|
-
for (const file of files) {
|
|
1794
|
-
lines.push(` ${file.originalName || file.filename}`);
|
|
1795
|
-
lines.push(` Size: ${formatSize(file.size)} | Type: ${file.fileType}`);
|
|
1796
|
-
lines.push(` URL: ${file.url}`);
|
|
1797
|
-
lines.push("");
|
|
1798
|
-
}
|
|
1799
|
-
console.log(lines.join("\n"));
|
|
1752
|
+
print(config, files, `Files in Sprint #${flags.sprint}:`);
|
|
1800
1753
|
}
|
|
1801
1754
|
});
|
|
1802
1755
|
}
|
|
@@ -1868,7 +1821,7 @@ function createFolderCreateCommand() {
|
|
|
1868
1821
|
console.error("Error: No API token configured.");
|
|
1869
1822
|
process.exit(1);
|
|
1870
1823
|
}
|
|
1871
|
-
const client = new ForLoopAPIClient({ token, baseUrl: config.apiUrl });
|
|
1824
|
+
const client = new ForLoopAPIClient({ token, baseUrl: config.apiUrl, timeout: 3e4 });
|
|
1872
1825
|
const story = await client.createStory({
|
|
1873
1826
|
title: flags.title,
|
|
1874
1827
|
description: flags.description,
|
|
@@ -1900,7 +1853,7 @@ async function getClient7() {
|
|
|
1900
1853
|
console.error("Error: No API token configured.");
|
|
1901
1854
|
process.exit(1);
|
|
1902
1855
|
}
|
|
1903
|
-
return { client: new ForLoopAPIClient({ token, baseUrl: config.apiUrl }) };
|
|
1856
|
+
return { client: new ForLoopAPIClient({ token, baseUrl: config.apiUrl, timeout: 3e4 }) };
|
|
1904
1857
|
}
|
|
1905
1858
|
function createSyncAivyFolderCommand() {
|
|
1906
1859
|
return defineCommand({
|
|
@@ -1949,7 +1902,7 @@ function createAivyDocGetCommand() {
|
|
|
1949
1902
|
{ flag: "--title <text>", description: 'Doc folder title (default: "forloop Aivy doc")' }
|
|
1950
1903
|
],
|
|
1951
1904
|
examples: ["forloop sync aivy-doc-get --sprint 1"],
|
|
1952
|
-
async run(
|
|
1905
|
+
async run(config, flags) {
|
|
1953
1906
|
const resolved = await getClient7();
|
|
1954
1907
|
if (!resolved) return;
|
|
1955
1908
|
const { client } = resolved;
|
|
@@ -1963,9 +1916,9 @@ function createAivyDocGetCommand() {
|
|
|
1963
1916
|
const stories = Array.isArray(sprint?.stories) ? sprint.stories : [];
|
|
1964
1917
|
const existing = stories.find((s) => s.type === "doc_folder" && s.title?.toLowerCase() === title.toLowerCase());
|
|
1965
1918
|
if (existing?.id) {
|
|
1966
|
-
|
|
1919
|
+
print(config, { docFolderId: existing.id, exists: true, title: existing.title, sprintId: resolution.sprintId }, `Doc folder: #${existing.id} "${existing.title}" (Sprint #${resolution.sprintId})`);
|
|
1967
1920
|
} else {
|
|
1968
|
-
|
|
1921
|
+
print(config, { exists: false, sprintId: resolution.sprintId }, `Doc folder not found. Run: forloop sync aivy-folder --sprint ${resolution.sprintId}`);
|
|
1969
1922
|
}
|
|
1970
1923
|
}
|
|
1971
1924
|
});
|
|
@@ -2028,6 +1981,7 @@ function createLocalToS3Command() {
|
|
|
2028
1981
|
{ flag: "--path <path>", description: "Local file path", required: true },
|
|
2029
1982
|
{ flag: "--sprint <id>", description: "Target sprint ID", type: "number" },
|
|
2030
1983
|
{ flag: "--folder <text>", description: "Remote folder (e.g., project/plans)" },
|
|
1984
|
+
{ flag: "--story-id <id>", description: "Link file to a story (e.g., doc_folder story)", type: "number" },
|
|
2031
1985
|
{ flag: "--delete", description: "Delete remote file instead of uploading" }
|
|
2032
1986
|
],
|
|
2033
1987
|
examples: ["forloop sync local-to-s3 --path ~/.forloop/sprint-1/plan/plan.md --sprint 1"],
|
|
@@ -2066,7 +2020,10 @@ function createLocalToS3Command() {
|
|
|
2066
2020
|
originalName: fileName,
|
|
2067
2021
|
fileType: contentType,
|
|
2068
2022
|
size: fileBuf.length,
|
|
2069
|
-
folder
|
|
2023
|
+
folder,
|
|
2024
|
+
x: 0,
|
|
2025
|
+
y: 0,
|
|
2026
|
+
storyId: flags.storyId
|
|
2070
2027
|
});
|
|
2071
2028
|
console.log(`Synced: ${fileName} -> Sprint #${resolution.sprintId}`);
|
|
2072
2029
|
}
|
|
@@ -2332,14 +2289,6 @@ var registry = new CommandRegistry({
|
|
|
2332
2289
|
"agent sprint": agentDeveloperSprintCmd
|
|
2333
2290
|
});
|
|
2334
2291
|
|
|
2335
|
-
// src/output/formatter.ts
|
|
2336
|
-
function formatError(message, format) {
|
|
2337
|
-
if (format === "json") {
|
|
2338
|
-
return JSON.stringify({ error: { message } }, null, 2);
|
|
2339
|
-
}
|
|
2340
|
-
return `\u274C ${message}`;
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
2292
|
// src/errors/handler.ts
|
|
2344
2293
|
function handleError(error) {
|
|
2345
2294
|
const isJson = process.argv.includes("--output=json");
|
|
@@ -2362,7 +2311,7 @@ Tip: ${suggestion}
|
|
|
2362
2311
|
}
|
|
2363
2312
|
|
|
2364
2313
|
// src/main.ts
|
|
2365
|
-
var CLI_VERSION = "0.2.
|
|
2314
|
+
var CLI_VERSION = "0.2.4";
|
|
2366
2315
|
process.on("SIGINT", () => {
|
|
2367
2316
|
process.stderr.write("\nInterrupted. Exiting.\n");
|
|
2368
2317
|
process.exit(130);
|