@lumoai/cli 1.4.0 → 1.5.1
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/assets/skill.md +228 -17
- package/dist/cli/src/commands/auth-login.js +4 -3
- package/dist/cli/src/commands/auth-logout.js +2 -1
- package/dist/cli/src/commands/doc-bind.js +3 -3
- package/dist/cli/src/commands/doc-create.js +5 -5
- package/dist/cli/src/commands/doc-delete.js +4 -4
- package/dist/cli/src/commands/doc-import-gdoc.js +82 -0
- package/dist/cli/src/commands/doc-list.js +7 -7
- package/dist/cli/src/commands/doc-move.js +8 -8
- package/dist/cli/src/commands/doc-share-list.js +11 -8
- package/dist/cli/src/commands/doc-share.js +7 -5
- package/dist/cli/src/commands/doc-show.js +6 -6
- package/dist/cli/src/commands/doc-sync.js +44 -0
- package/dist/cli/src/commands/doc-unbind.js +4 -4
- package/dist/cli/src/commands/doc-unshare.js +9 -7
- package/dist/cli/src/commands/doc-update.js +5 -5
- package/dist/cli/src/commands/hook.js +2 -2
- package/dist/cli/src/commands/memory-project-add.js +86 -0
- package/dist/cli/src/commands/memory-project-list.js +58 -0
- package/dist/cli/src/commands/memory-promote.js +52 -0
- package/dist/cli/src/commands/memory-rm.js +42 -0
- package/dist/cli/src/commands/memory-task-add.js +99 -0
- package/dist/cli/src/commands/memory-task-list.js +61 -0
- package/dist/cli/src/commands/milestone-create.js +4 -4
- package/dist/cli/src/commands/milestone-delete.js +5 -5
- package/dist/cli/src/commands/milestone-list.js +3 -3
- package/dist/cli/src/commands/milestone-show.js +5 -5
- package/dist/cli/src/commands/milestone-update.js +6 -5
- package/dist/cli/src/commands/project-list.js +3 -3
- package/dist/cli/src/commands/session-attach.js +5 -5
- package/dist/cli/src/commands/session-detach.js +3 -3
- package/dist/cli/src/commands/session-status.js +3 -3
- package/dist/cli/src/commands/setup.js +33 -7
- package/dist/cli/src/commands/sprint-add.js +3 -3
- package/dist/cli/src/commands/sprint-close.js +5 -5
- package/dist/cli/src/commands/sprint-create.js +4 -4
- package/dist/cli/src/commands/sprint-delete.js +5 -5
- package/dist/cli/src/commands/sprint-list.js +3 -3
- package/dist/cli/src/commands/sprint-remove.js +3 -3
- package/dist/cli/src/commands/sprint-show.js +4 -4
- package/dist/cli/src/commands/sprint-start.js +4 -4
- package/dist/cli/src/commands/sprint-summary.js +7 -7
- package/dist/cli/src/commands/sprint-update.js +6 -5
- package/dist/cli/src/commands/task-artifact-add.js +35 -6
- package/dist/cli/src/commands/task-artifact-list.js +5 -3
- package/dist/cli/src/commands/task-artifact-rm.js +4 -4
- package/dist/cli/src/commands/task-artifact-show.js +9 -7
- package/dist/cli/src/commands/task-artifact-update.js +15 -6
- package/dist/cli/src/commands/task-comment-list.js +111 -0
- package/dist/cli/src/commands/task-comment.js +3 -3
- package/dist/cli/src/commands/task-context.js +24 -12
- package/dist/cli/src/commands/task-create.js +7 -7
- package/dist/cli/src/commands/task-figma-add.js +3 -2
- package/dist/cli/src/commands/task-figma-context.js +61 -0
- package/dist/cli/src/commands/task-figma-list.js +3 -2
- package/dist/cli/src/commands/task-figma-refresh.js +4 -3
- package/dist/cli/src/commands/task-figma-rm.js +3 -2
- package/dist/cli/src/commands/task-list.js +1 -2
- package/dist/cli/src/commands/task-pr-show.js +66 -0
- package/dist/cli/src/commands/task-show.js +8 -7
- package/dist/cli/src/commands/task-slack-show.js +59 -0
- package/dist/cli/src/commands/task-update.js +7 -7
- package/dist/cli/src/commands/task-web-show.js +64 -0
- package/dist/cli/src/commands/whoami.js +4 -3
- package/dist/cli/src/index.js +239 -102
- package/dist/cli/src/lib/agent.js +58 -0
- package/dist/cli/src/lib/api.js +81 -1
- package/dist/cli/src/lib/config.js +2 -1
- package/dist/cli/src/lib/doc-input.js +12 -1
- package/dist/cli/src/lib/figma-api.js +1 -1
- package/dist/cli/src/lib/format.js +3 -2
- package/dist/cli/src/lib/hook-runner.js +26 -10
- package/dist/cli/src/lib/hooks-template.js +52 -7
- package/dist/cli/src/lib/memory-content.js +88 -0
- package/dist/cli/src/lib/path-guard.js +125 -0
- package/dist/cli/src/lib/resolve-bound-task.js +31 -0
- package/dist/cli/src/lib/resolve-doc-id.js +2 -1
- package/dist/cli/src/lib/resolve-member.js +2 -1
- package/dist/cli/src/lib/resolve-project.js +24 -0
- package/dist/cli/src/lib/sanitize.js +17 -0
- package/dist/cli/src/lib/tag-resolver.js +2 -1
- package/dist/cli/src/lib/update-check.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoryTaskAdd = memoryTaskAdd;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
7
|
+
const memory_content_1 = require("../lib/memory-content");
|
|
8
|
+
const resolve_bound_task_1 = require("../lib/resolve-bound-task");
|
|
9
|
+
const agent_1 = require("../lib/agent");
|
|
10
|
+
async function memoryTaskAdd(identifierArg, options) {
|
|
11
|
+
if (!options.category) {
|
|
12
|
+
console.error('Error: --category <trap|decision|convention|procedural> is required.');
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
15
|
+
const built = (0, memory_content_1.buildMemoryContent)(options.category, options);
|
|
16
|
+
if (!built.ok) {
|
|
17
|
+
console.error(`Error: ${built.error}`);
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
let agent;
|
|
21
|
+
const agentRaw = options.agent?.trim();
|
|
22
|
+
if (agentRaw) {
|
|
23
|
+
const normalized = (0, agent_1.normalizeAgent)(agentRaw);
|
|
24
|
+
if (!normalized) {
|
|
25
|
+
console.error(`Error: invalid --agent "${agentRaw}". Valid values: ${agent_1.VALID_AGENT_TOKENS.join(', ')}.`);
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
agent = normalized;
|
|
29
|
+
}
|
|
30
|
+
const creds = (0, config_1.readCredentials)();
|
|
31
|
+
if (!creds) {
|
|
32
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
36
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
37
|
+
// Resolve the target: explicit arg > session-bound task.
|
|
38
|
+
const identifier = identifierArg ?? (await (0, resolve_bound_task_1.resolveBoundTaskIdentifier)(apiUrl, creds.token));
|
|
39
|
+
if (!identifier) {
|
|
40
|
+
console.error('Error: no <LUM-N> given and no task bound to this session.\n' +
|
|
41
|
+
'Pass it explicitly or run `lumo session attach <LUM-N>`.');
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
// LUM-N → task id.
|
|
45
|
+
let taskId;
|
|
46
|
+
try {
|
|
47
|
+
const r = await fetch(`${base}/api/tasks/resolve/${encodeURIComponent(identifier)}`, {
|
|
48
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
49
|
+
});
|
|
50
|
+
if (r.status === 401) {
|
|
51
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
if (r.status === 404) {
|
|
55
|
+
console.error(`Error: task ${identifier} not found in workspace ${creds.workspaceSlug}`);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
if (!r.ok) {
|
|
59
|
+
console.error(`Error: resolve failed (HTTP ${r.status})`);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
taskId = (await r.json()).id;
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
// POST the memory.
|
|
69
|
+
let res;
|
|
70
|
+
try {
|
|
71
|
+
res = await fetch(`${base}/api/tasks/${encodeURIComponent(taskId)}/memories`, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { Authorization: `Bearer ${creds.token}`, 'Content-Type': 'application/json' },
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
category: built.category,
|
|
76
|
+
content: built.content,
|
|
77
|
+
...(agent ? { agent } : {}),
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
if (res.status !== 201) {
|
|
86
|
+
let m = null;
|
|
87
|
+
try {
|
|
88
|
+
const b = (await res.json());
|
|
89
|
+
if (typeof b.error === 'string')
|
|
90
|
+
m = b.error;
|
|
91
|
+
}
|
|
92
|
+
catch { /* */ }
|
|
93
|
+
console.error(m ? `Error: ${(0, sanitize_1.sanitizeField)(m)}` : `Error: memory add failed (HTTP ${res.status})`);
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
process.stdout.write(`Added ${built.category} memory to ${identifier}` +
|
|
97
|
+
`${identifierArg ? '' : ' (from bound session)'}\n` +
|
|
98
|
+
'Tip: only useful for this task? If it generalizes, use `lumo project memory add` or `lumo memory promote` later.\n');
|
|
99
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoryTaskList = memoryTaskList;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const memory_content_1 = require("../lib/memory-content");
|
|
7
|
+
const resolve_bound_task_1 = require("../lib/resolve-bound-task");
|
|
8
|
+
async function memoryTaskList(identifierArg, options) {
|
|
9
|
+
const limit = options.limit !== undefined ? parseInt(options.limit, 10) : undefined;
|
|
10
|
+
if (limit !== undefined && (Number.isNaN(limit) || limit < 1)) {
|
|
11
|
+
console.error(`Error: invalid --limit "${options.limit}" (expected a positive integer)`);
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
const creds = (0, config_1.readCredentials)();
|
|
15
|
+
if (!creds) {
|
|
16
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
20
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
21
|
+
const identifier = identifierArg ?? (await (0, resolve_bound_task_1.resolveBoundTaskIdentifier)(apiUrl, creds.token));
|
|
22
|
+
if (!identifier) {
|
|
23
|
+
console.error('Error: no <LUM-N> given and no task bound to this session.\nPass it explicitly or run `lumo session attach <LUM-N>`.');
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
let taskId;
|
|
27
|
+
try {
|
|
28
|
+
const r = await fetch(`${base}/api/tasks/resolve/${encodeURIComponent(identifier)}`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
29
|
+
if (r.status === 404) {
|
|
30
|
+
console.error(`Error: task ${identifier} not found in workspace ${creds.workspaceSlug}`);
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
if (!r.ok) {
|
|
34
|
+
console.error(`Error: resolve failed (HTTP ${r.status})`);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
taskId = (await r.json()).id;
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
let res;
|
|
44
|
+
try {
|
|
45
|
+
res = await fetch(`${base}/api/tasks/${encodeURIComponent(taskId)}/memories`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
console.error(`Error: memory list failed (HTTP ${res.status})`);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
let rows = (await res.json()).memories;
|
|
56
|
+
if (options.category)
|
|
57
|
+
rows = rows.filter(m => m.category.toLowerCase() === options.category.toLowerCase());
|
|
58
|
+
if (limit !== undefined)
|
|
59
|
+
rows = rows.slice(0, limit);
|
|
60
|
+
process.stdout.write((0, memory_content_1.formatMemoryList)(rows) + '\n');
|
|
61
|
+
}
|
|
@@ -5,6 +5,7 @@ exports.formatCreatedMilestoneLine = formatCreatedMilestoneLine;
|
|
|
5
5
|
exports.milestoneCreate = milestoneCreate;
|
|
6
6
|
const config_1 = require("../lib/config");
|
|
7
7
|
const api_1 = require("../lib/api");
|
|
8
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
8
9
|
const resolve_1 = require("../lib/resolve");
|
|
9
10
|
function buildCreatePayload(name, opts) {
|
|
10
11
|
const payload = { name };
|
|
@@ -17,7 +18,7 @@ function buildCreatePayload(name, opts) {
|
|
|
17
18
|
return payload;
|
|
18
19
|
}
|
|
19
20
|
function formatCreatedMilestoneLine(milestone) {
|
|
20
|
-
return `Created milestone "${milestone.name}" ${milestone.id}`;
|
|
21
|
+
return `Created milestone "${(0, sanitize_1.sanitizeField)(milestone.name)}" ${milestone.id}`;
|
|
21
22
|
}
|
|
22
23
|
async function milestoneCreate(name, opts) {
|
|
23
24
|
if (!name || !name.trim()) {
|
|
@@ -29,8 +30,7 @@ async function milestoneCreate(name, opts) {
|
|
|
29
30
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
30
31
|
return 1;
|
|
31
32
|
}
|
|
32
|
-
const
|
|
33
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
33
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
34
34
|
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
35
35
|
let projectId;
|
|
36
36
|
try {
|
|
@@ -72,7 +72,7 @@ async function milestoneCreate(name, opts) {
|
|
|
72
72
|
// Body wasn't JSON; fall through to status-only message
|
|
73
73
|
}
|
|
74
74
|
if (serverMsg) {
|
|
75
|
-
console.error(`Error: ${serverMsg}`);
|
|
75
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`);
|
|
76
76
|
}
|
|
77
77
|
else {
|
|
78
78
|
console.error(`Error: milestone create failed (HTTP ${res.status})`);
|
|
@@ -5,8 +5,9 @@ exports.milestoneDelete = milestoneDelete;
|
|
|
5
5
|
const config_1 = require("../lib/config");
|
|
6
6
|
const api_1 = require("../lib/api");
|
|
7
7
|
const resolve_1 = require("../lib/resolve");
|
|
8
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
8
9
|
function formatDeleteRefusal(name, taskCount) {
|
|
9
|
-
const head = `Refusing to delete milestone "${name}" without --yes.`;
|
|
10
|
+
const head = `Refusing to delete milestone "${(0, sanitize_1.sanitizeField)(name)}" without --yes.`;
|
|
10
11
|
const tail = `Re-run with --yes to confirm.`;
|
|
11
12
|
if (taskCount === 0)
|
|
12
13
|
return `${head}\n${tail}`;
|
|
@@ -21,8 +22,7 @@ async function milestoneDelete(identifier, opts) {
|
|
|
21
22
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
22
23
|
return 1;
|
|
23
24
|
}
|
|
24
|
-
const
|
|
25
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
25
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
26
26
|
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
27
27
|
let milestoneId;
|
|
28
28
|
let resolvedName;
|
|
@@ -89,8 +89,8 @@ async function milestoneDelete(identifier, opts) {
|
|
|
89
89
|
catch {
|
|
90
90
|
// body wasn't JSON
|
|
91
91
|
}
|
|
92
|
-
console.error(`Error: ${errMsg}`);
|
|
92
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
|
|
93
93
|
return 1;
|
|
94
94
|
}
|
|
95
|
-
process.stdout.write(`Deleted milestone "${name}"\n`);
|
|
95
|
+
process.stdout.write(`Deleted milestone "${(0, sanitize_1.sanitizeField)(name)}"\n`);
|
|
96
96
|
}
|
|
@@ -5,6 +5,7 @@ exports.milestoneList = milestoneList;
|
|
|
5
5
|
const config_1 = require("../lib/config");
|
|
6
6
|
const api_1 = require("../lib/api");
|
|
7
7
|
const resolve_1 = require("../lib/resolve");
|
|
8
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
8
9
|
function formatDate(iso) {
|
|
9
10
|
if (!iso)
|
|
10
11
|
return '-';
|
|
@@ -22,7 +23,7 @@ function formatMilestoneList(rows) {
|
|
|
22
23
|
const statusW = Math.max(...rows.map(r => r.status.length));
|
|
23
24
|
const dateW = Math.max(...rows.map(r => formatDate(r.targetDate).length));
|
|
24
25
|
return rows
|
|
25
|
-
.map(r => `${r.status.padEnd(statusW)} ${formatDate(r.targetDate).padEnd(dateW)} ${r.name}`)
|
|
26
|
+
.map(r => `${r.status.padEnd(statusW)} ${formatDate(r.targetDate).padEnd(dateW)} ${(0, sanitize_1.sanitizeField)(r.name)}`)
|
|
26
27
|
.join('\n');
|
|
27
28
|
}
|
|
28
29
|
async function milestoneList(options) {
|
|
@@ -31,8 +32,7 @@ async function milestoneList(options) {
|
|
|
31
32
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
32
33
|
return 1;
|
|
33
34
|
}
|
|
34
|
-
const
|
|
35
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
35
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
36
36
|
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
37
37
|
let projectId;
|
|
38
38
|
try {
|
|
@@ -6,6 +6,7 @@ const config_1 = require("../lib/config");
|
|
|
6
6
|
const api_1 = require("../lib/api");
|
|
7
7
|
const resolve_1 = require("../lib/resolve");
|
|
8
8
|
const format_1 = require("../lib/format");
|
|
9
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
9
10
|
function fmtDate(iso) {
|
|
10
11
|
return iso ? iso.slice(0, 10) : '-';
|
|
11
12
|
}
|
|
@@ -15,13 +16,13 @@ function formatMilestoneShow(m, tasks) {
|
|
|
15
16
|
m.taskCounts.IN_REVIEW +
|
|
16
17
|
m.taskCounts.DONE;
|
|
17
18
|
const lines = [
|
|
18
|
-
`Milestone: ${m.name}`,
|
|
19
|
+
`Milestone: ${(0, sanitize_1.sanitizeField)(m.name)}`,
|
|
19
20
|
`Status: ${m.status}`,
|
|
20
21
|
`Start: ${fmtDate(m.startDate)}`,
|
|
21
22
|
`Target: ${fmtDate(m.targetDate)}`,
|
|
22
|
-
`Project: ${m.projectName}`,
|
|
23
|
+
`Project: ${(0, sanitize_1.sanitizeField)(m.projectName)}`,
|
|
23
24
|
`Description:`,
|
|
24
|
-
` ${m.description && m.description.length > 0 ? m.description : '-'}`,
|
|
25
|
+
` ${m.description && m.description.length > 0 ? (0, sanitize_1.sanitizeField)(m.description) : '-'}`,
|
|
25
26
|
``,
|
|
26
27
|
`Tasks: ${total} total (TODO ${m.taskCounts.TODO} / IN_PROGRESS ${m.taskCounts.IN_PROGRESS} / IN_REVIEW ${m.taskCounts.IN_REVIEW} / DONE ${m.taskCounts.DONE})`,
|
|
27
28
|
];
|
|
@@ -36,8 +37,7 @@ async function milestoneShow(identifier, opts) {
|
|
|
36
37
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
37
38
|
return 1;
|
|
38
39
|
}
|
|
39
|
-
const
|
|
40
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
40
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
41
41
|
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
42
42
|
let milestoneId;
|
|
43
43
|
try {
|
|
@@ -7,6 +7,7 @@ exports.milestoneUpdate = milestoneUpdate;
|
|
|
7
7
|
const config_1 = require("../lib/config");
|
|
8
8
|
const api_1 = require("../lib/api");
|
|
9
9
|
const resolve_1 = require("../lib/resolve");
|
|
10
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
10
11
|
const ALLOWED_STATUSES = [
|
|
11
12
|
'PLANNED',
|
|
12
13
|
'ACTIVE',
|
|
@@ -54,9 +55,10 @@ function fmtDate(v) {
|
|
|
54
55
|
return v.slice(0, 10);
|
|
55
56
|
}
|
|
56
57
|
function formatMilestoneUpdateSummary(before, after) {
|
|
58
|
+
const beforeName = (0, sanitize_1.sanitizeField)(before.name);
|
|
57
59
|
const changes = [];
|
|
58
60
|
if (after.name !== undefined && after.name !== before.name) {
|
|
59
|
-
changes.push(`name "${
|
|
61
|
+
changes.push(`name "${beforeName}" → "${(0, sanitize_1.sanitizeField)(after.name)}"`);
|
|
60
62
|
}
|
|
61
63
|
if (after.description !== undefined) {
|
|
62
64
|
changes.push(after.description === null ? 'description → ∅' : 'description updated');
|
|
@@ -70,7 +72,7 @@ function formatMilestoneUpdateSummary(before, after) {
|
|
|
70
72
|
if (after.targetDate !== undefined) {
|
|
71
73
|
changes.push(`target → ${fmtDate(after.targetDate)}`);
|
|
72
74
|
}
|
|
73
|
-
return `Updated milestone "${
|
|
75
|
+
return `Updated milestone "${beforeName}": ${changes.join(', ')}`;
|
|
74
76
|
}
|
|
75
77
|
async function milestoneUpdate(identifier, opts) {
|
|
76
78
|
// Validate status flag eagerly.
|
|
@@ -96,8 +98,7 @@ async function milestoneUpdate(identifier, opts) {
|
|
|
96
98
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
97
99
|
return 1;
|
|
98
100
|
}
|
|
99
|
-
const
|
|
100
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
101
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
101
102
|
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
102
103
|
let milestoneId;
|
|
103
104
|
try {
|
|
@@ -160,7 +161,7 @@ async function milestoneUpdate(identifier, opts) {
|
|
|
160
161
|
catch {
|
|
161
162
|
// body wasn't JSON; keep the status-only message
|
|
162
163
|
}
|
|
163
|
-
console.error(`Error: ${errMsg}`);
|
|
164
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
|
|
164
165
|
return 1;
|
|
165
166
|
}
|
|
166
167
|
process.stdout.write(formatMilestoneUpdateSummary(before, payload) + '\n');
|
|
@@ -5,6 +5,7 @@ exports.projectList = projectList;
|
|
|
5
5
|
const config_1 = require("../lib/config");
|
|
6
6
|
const api_1 = require("../lib/api");
|
|
7
7
|
const resolve_1 = require("../lib/resolve");
|
|
8
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
8
9
|
/**
|
|
9
10
|
* Render projects as `slug-style-name Display Name` lines. The slug form
|
|
10
11
|
* matches what `lumo task create --project <ref>` accepts (slugify(name)),
|
|
@@ -22,7 +23,7 @@ function formatProjectList(projects) {
|
|
|
22
23
|
if (rows.length === 0)
|
|
23
24
|
return 'No projects.';
|
|
24
25
|
const w = Math.max(...rows.map(r => r.slug.length));
|
|
25
|
-
return rows.map(r => `${r.slug.padEnd(w)} ${r.name}`).join('\n');
|
|
26
|
+
return rows.map(r => `${r.slug.padEnd(w)} ${(0, sanitize_1.sanitizeField)(r.name)}`).join('\n');
|
|
26
27
|
}
|
|
27
28
|
async function projectList() {
|
|
28
29
|
const creds = (0, config_1.readCredentials)();
|
|
@@ -30,8 +31,7 @@ async function projectList() {
|
|
|
30
31
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
31
32
|
return 1;
|
|
32
33
|
}
|
|
33
|
-
const
|
|
34
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
34
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
35
35
|
const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/projects`;
|
|
36
36
|
let res;
|
|
37
37
|
try {
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.sessionAttach = sessionAttach;
|
|
4
4
|
const config_1 = require("../lib/config");
|
|
5
5
|
const api_1 = require("../lib/api");
|
|
6
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
6
7
|
/**
|
|
7
8
|
* `lumo session attach <identifier>` — bind the currently-running
|
|
8
9
|
* Claude Code session to a task.
|
|
@@ -30,8 +31,7 @@ async function sessionAttach(identifier) {
|
|
|
30
31
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
31
32
|
return 1;
|
|
32
33
|
}
|
|
33
|
-
const
|
|
34
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
34
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
35
35
|
const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/sessions/${encodeURIComponent(sessionId)}/bind-task`;
|
|
36
36
|
let res;
|
|
37
37
|
try {
|
|
@@ -63,7 +63,7 @@ async function sessionAttach(identifier) {
|
|
|
63
63
|
catch {
|
|
64
64
|
// fall through
|
|
65
65
|
}
|
|
66
|
-
console.error(`Error: ${message}`);
|
|
66
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(message)}`);
|
|
67
67
|
return 1;
|
|
68
68
|
}
|
|
69
69
|
if (!res.ok) {
|
|
@@ -71,10 +71,10 @@ async function sessionAttach(identifier) {
|
|
|
71
71
|
return 1;
|
|
72
72
|
}
|
|
73
73
|
const result = (await res.json());
|
|
74
|
-
console.log(`Attached session ${sessionId} to ${result.taskIdentifier} "${result.taskTitle}"`);
|
|
74
|
+
console.log(`Attached session ${sessionId} to ${result.taskIdentifier} "${(0, sanitize_1.sanitizeField)(result.taskTitle)}"`);
|
|
75
75
|
console.log(`Re-tagged ${result.retaggedEventCount} previously-untagged event${result.retaggedEventCount === 1 ? '' : 's'} in this session.`);
|
|
76
76
|
if (result.memorySection !== '') {
|
|
77
77
|
console.log('');
|
|
78
|
-
console.log(result.memorySection);
|
|
78
|
+
console.log((0, sanitize_1.sanitizeField)(result.memorySection));
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.sessionDetach = sessionDetach;
|
|
4
4
|
const config_1 = require("../lib/config");
|
|
5
5
|
const api_1 = require("../lib/api");
|
|
6
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
6
7
|
/**
|
|
7
8
|
* `lumo session detach` — clear the task binding on the current Claude Code
|
|
8
9
|
* session. Idempotent: re-detaching an already-unbound session reports
|
|
@@ -24,8 +25,7 @@ async function sessionDetach() {
|
|
|
24
25
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
25
26
|
return 1;
|
|
26
27
|
}
|
|
27
|
-
const
|
|
28
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
28
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
29
29
|
const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/sessions/${encodeURIComponent(sessionId)}/bind-task`;
|
|
30
30
|
let res;
|
|
31
31
|
try {
|
|
@@ -56,5 +56,5 @@ async function sessionDetach() {
|
|
|
56
56
|
process.stdout.write(`Session ${sessionId} was already unbound.\n`);
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
|
-
process.stdout.write(`Detached session ${sessionId} from ${data.previousTaskIdentifier} "${data.previousTaskTitle}".\n`);
|
|
59
|
+
process.stdout.write(`Detached session ${sessionId} from ${data.previousTaskIdentifier} "${(0, sanitize_1.sanitizeField)(data.previousTaskTitle ?? '')}".\n`);
|
|
60
60
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.sessionStatus = sessionStatus;
|
|
4
4
|
const config_1 = require("../lib/config");
|
|
5
5
|
const api_1 = require("../lib/api");
|
|
6
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
6
7
|
async function sessionStatus() {
|
|
7
8
|
const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
8
9
|
if (!sessionId) {
|
|
@@ -15,8 +16,7 @@ async function sessionStatus() {
|
|
|
15
16
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
16
17
|
return 1;
|
|
17
18
|
}
|
|
18
|
-
const
|
|
19
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
19
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
20
20
|
const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
21
21
|
let res;
|
|
22
22
|
try {
|
|
@@ -47,7 +47,7 @@ async function sessionStatus() {
|
|
|
47
47
|
const data = (await res.json());
|
|
48
48
|
if (data.taskIdentifier && data.taskTitle) {
|
|
49
49
|
process.stdout.write(`Session ${sessionId}\n` +
|
|
50
|
-
` Bound to: ${data.taskIdentifier} "${data.taskTitle}"\n` +
|
|
50
|
+
` Bound to: ${data.taskIdentifier} "${(0, sanitize_1.sanitizeField)(data.taskTitle)}"\n` +
|
|
51
51
|
` Events: ${data.eventCount}\n`);
|
|
52
52
|
}
|
|
53
53
|
else {
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveAgentToken = resolveAgentToken;
|
|
36
37
|
exports.setup = setup;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const os = __importStar(require("os"));
|
|
@@ -40,11 +41,35 @@ const path = __importStar(require("path"));
|
|
|
40
41
|
const child_process_1 = require("child_process");
|
|
41
42
|
const hooks_template_1 = require("../lib/hooks-template");
|
|
42
43
|
const line_prompt_1 = require("../lib/line-prompt");
|
|
44
|
+
const agent_1 = require("../lib/agent");
|
|
45
|
+
const config_1 = require("../lib/config");
|
|
46
|
+
/**
|
|
47
|
+
* Validate the `--agent` flag and canonicalize it to the CLI token baked into
|
|
48
|
+
* the hook commands. Defaults to `claude-code` — accurate today, since only
|
|
49
|
+
* Claude Code emits hook events. Returns `{ error }` for an unrecognized
|
|
50
|
+
* token so the caller can abort before writing settings.json.
|
|
51
|
+
*/
|
|
52
|
+
function resolveAgentToken(raw) {
|
|
53
|
+
if (raw === undefined)
|
|
54
|
+
return { token: 'claude-code' };
|
|
55
|
+
const enumValue = (0, agent_1.normalizeAgent)(raw);
|
|
56
|
+
if (!enumValue) {
|
|
57
|
+
return {
|
|
58
|
+
error: `Unknown agent "${raw}". Valid agents: ${agent_1.VALID_AGENT_TOKENS.join(', ')}.`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { token: agent_1.ENUM_TO_TOKEN[enumValue] };
|
|
62
|
+
}
|
|
43
63
|
async function setup(options) {
|
|
44
64
|
if (options.user && options.project) {
|
|
45
65
|
process.stderr.write('Error: --user and --project are mutually exclusive.\n');
|
|
46
66
|
return 1;
|
|
47
67
|
}
|
|
68
|
+
const agentResult = resolveAgentToken(options.agent);
|
|
69
|
+
if ('error' in agentResult) {
|
|
70
|
+
process.stderr.write(`Error: ${agentResult.error}\n`);
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
48
73
|
const scope = await resolveScope(options);
|
|
49
74
|
if (!scope)
|
|
50
75
|
return 130;
|
|
@@ -52,7 +77,7 @@ async function setup(options) {
|
|
|
52
77
|
const claudeDir = path.join(root, '.claude');
|
|
53
78
|
process.stdout.write(`\nInstalling Lumo for ${scope} scope: ${claudeDir}\n\n`);
|
|
54
79
|
installSkill(claudeDir, options.force === true);
|
|
55
|
-
mergeSettings(claudeDir);
|
|
80
|
+
mergeSettings(claudeDir, agentResult.token);
|
|
56
81
|
printPostInstall();
|
|
57
82
|
return 0;
|
|
58
83
|
}
|
|
@@ -101,7 +126,7 @@ function installSkill(claudeDir, force) {
|
|
|
101
126
|
fs.copyFileSync(skillSrc, skillDst);
|
|
102
127
|
process.stdout.write(`✓ wrote skill: ${skillDst}\n`);
|
|
103
128
|
}
|
|
104
|
-
function mergeSettings(claudeDir) {
|
|
129
|
+
function mergeSettings(claudeDir, agentToken) {
|
|
105
130
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
106
131
|
let existing = {};
|
|
107
132
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -116,18 +141,19 @@ function mergeSettings(claudeDir) {
|
|
|
116
141
|
else {
|
|
117
142
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
118
143
|
}
|
|
119
|
-
const { merged, stats } = (0, hooks_template_1.mergeLumoHooks)(existing);
|
|
144
|
+
const { merged, stats } = (0, hooks_template_1.mergeLumoHooks)(existing, agentToken);
|
|
120
145
|
fs.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + '\n');
|
|
121
|
-
|
|
122
|
-
|
|
146
|
+
const changed = stats.addedEvents.length + stats.updatedEvents.length;
|
|
147
|
+
if (changed === 0) {
|
|
148
|
+
process.stdout.write(`✓ hooks already wired in (agent=${agentToken}): ${settingsPath} (${stats.alreadyPresent.length} events)\n`);
|
|
123
149
|
}
|
|
124
150
|
else {
|
|
125
|
-
process.stdout.write(`✓ merged hooks into ${settingsPath} (added ${stats.addedEvents.length}, kept ${stats.alreadyPresent.length})\n`);
|
|
151
|
+
process.stdout.write(`✓ merged hooks into ${settingsPath} (agent=${agentToken}; added ${stats.addedEvents.length}, updated ${stats.updatedEvents.length}, kept ${stats.alreadyPresent.length})\n`);
|
|
126
152
|
}
|
|
127
153
|
}
|
|
128
154
|
function printPostInstall() {
|
|
129
155
|
const onPath = isLumoOnPath();
|
|
130
|
-
const credsPath = path.join(
|
|
156
|
+
const credsPath = path.join((0, config_1.configDir)(), 'credentials.json');
|
|
131
157
|
const authed = fs.existsSync(credsPath);
|
|
132
158
|
process.stdout.write('\nNext steps:\n');
|
|
133
159
|
if (!onPath || isRunningUnderNpx()) {
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.sprintAdd = sprintAdd;
|
|
4
4
|
const config_1 = require("../lib/config");
|
|
5
5
|
const api_1 = require("../lib/api");
|
|
6
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
6
7
|
const resolve_1 = require("../lib/resolve");
|
|
7
8
|
async function sprintAdd(identifier, taskIdentifier, opts) {
|
|
8
9
|
const creds = (0, config_1.readCredentials)();
|
|
@@ -10,8 +11,7 @@ async function sprintAdd(identifier, taskIdentifier, opts) {
|
|
|
10
11
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
11
12
|
return 1;
|
|
12
13
|
}
|
|
13
|
-
const
|
|
14
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
14
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
15
15
|
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
16
16
|
const workspaceSlug = creds.workspaceSlug ?? '';
|
|
17
17
|
let resolved;
|
|
@@ -55,7 +55,7 @@ async function sprintAdd(identifier, taskIdentifier, opts) {
|
|
|
55
55
|
catch {
|
|
56
56
|
// ignore
|
|
57
57
|
}
|
|
58
|
-
console.error(`Error: ${errMsg}`);
|
|
58
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
|
|
59
59
|
return 1;
|
|
60
60
|
}
|
|
61
61
|
process.stdout.write(`Added ${taskIdentifier} to sprint #${resolved.number}\n`);
|
|
@@ -7,6 +7,7 @@ const config_1 = require("../lib/config");
|
|
|
7
7
|
const api_1 = require("../lib/api");
|
|
8
8
|
const resolve_1 = require("../lib/resolve");
|
|
9
9
|
const format_1 = require("../lib/format");
|
|
10
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
10
11
|
/**
|
|
11
12
|
* Build the decisions map for POST /api/sprints/<id>/close.
|
|
12
13
|
* Only non-DONE tasks get a decision entry; DONE tasks are excluded entirely.
|
|
@@ -28,7 +29,7 @@ function buildCloseDecisions(tasks, mode) {
|
|
|
28
29
|
function formatUnfinishedRefusal(number, name, tasks) {
|
|
29
30
|
const unfinished = tasks.filter(t => t.status !== 'DONE');
|
|
30
31
|
const lines = [
|
|
31
|
-
`Refusing to close sprint #${number} "${name}": ${unfinished.length} task(s) not DONE.`,
|
|
32
|
+
`Refusing to close sprint #${number} "${(0, sanitize_1.sanitizeField)(name)}": ${unfinished.length} task(s) not DONE.`,
|
|
32
33
|
'',
|
|
33
34
|
(0, format_1.formatTaskListTable)(unfinished),
|
|
34
35
|
'',
|
|
@@ -56,8 +57,7 @@ async function sprintClose(identifier, opts) {
|
|
|
56
57
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
57
58
|
return 1;
|
|
58
59
|
}
|
|
59
|
-
const
|
|
60
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
60
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
61
61
|
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
62
62
|
const workspaceSlug = creds.workspaceSlug ?? '';
|
|
63
63
|
let resolved;
|
|
@@ -144,8 +144,8 @@ async function sprintClose(identifier, opts) {
|
|
|
144
144
|
catch {
|
|
145
145
|
// body wasn't JSON
|
|
146
146
|
}
|
|
147
|
-
console.error(`Error: ${errMsg}`);
|
|
147
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
|
|
148
148
|
return 1;
|
|
149
149
|
}
|
|
150
|
-
process.stdout.write(`Closed sprint #${sprint.number} "${sprint.name}"\n`);
|
|
150
|
+
process.stdout.write(`Closed sprint #${sprint.number} "${(0, sanitize_1.sanitizeField)(sprint.name)}"\n`);
|
|
151
151
|
}
|
|
@@ -6,6 +6,7 @@ exports.sprintCreate = sprintCreate;
|
|
|
6
6
|
const config_1 = require("../lib/config");
|
|
7
7
|
const api_1 = require("../lib/api");
|
|
8
8
|
const resolve_1 = require("../lib/resolve");
|
|
9
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
9
10
|
function buildCreateSprintPayload(opts) {
|
|
10
11
|
// start/end are required by the server schema; caller validates presence.
|
|
11
12
|
const payload = {
|
|
@@ -17,7 +18,7 @@ function buildCreateSprintPayload(opts) {
|
|
|
17
18
|
return payload;
|
|
18
19
|
}
|
|
19
20
|
function formatCreatedSprintLine(sprint) {
|
|
20
|
-
return `Created sprint #${sprint.number} "${sprint.name}" ${sprint.id}`;
|
|
21
|
+
return `Created sprint #${sprint.number} "${(0, sanitize_1.sanitizeField)(sprint.name)}" ${sprint.id}`;
|
|
21
22
|
}
|
|
22
23
|
async function sprintCreate(opts) {
|
|
23
24
|
if (!opts.start || !opts.end) {
|
|
@@ -29,8 +30,7 @@ async function sprintCreate(opts) {
|
|
|
29
30
|
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
30
31
|
return 1;
|
|
31
32
|
}
|
|
32
|
-
const
|
|
33
|
-
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
33
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
34
34
|
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
35
35
|
let teamId;
|
|
36
36
|
try {
|
|
@@ -72,7 +72,7 @@ async function sprintCreate(opts) {
|
|
|
72
72
|
catch {
|
|
73
73
|
// ignore
|
|
74
74
|
}
|
|
75
|
-
console.error(`Error: ${serverMsg
|
|
75
|
+
console.error(`Error: ${serverMsg != null ? (0, sanitize_1.sanitizeField)(serverMsg) : `sprint create failed (HTTP ${res.status})`}`);
|
|
76
76
|
return 1;
|
|
77
77
|
}
|
|
78
78
|
const sprint = (await res.json());
|