spets 0.1.86 → 0.1.88
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/chunk-5MG5ZRBD.js +164 -0
- package/dist/chunk-DVDS7BTW.js +542 -0
- package/dist/chunk-PNREKOWG.js +112 -0
- package/dist/chunk-VQV22N3Y.js +69 -0
- package/dist/chunk-YGKZZIN2.js +213 -0
- package/dist/config-KMO3LQUF.js +13 -0
- package/dist/docs-QNQOL4I6.js +9 -0
- package/dist/index.js +628 -886
- package/dist/knowledge-THGVNVOA.js +9 -0
- package/dist/tasks-3SJVPAZR.js +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearConfigCache,
|
|
3
|
+
getConfigPath,
|
|
4
|
+
loadConfig
|
|
5
|
+
} from "./chunk-VQV22N3Y.js";
|
|
6
|
+
|
|
7
|
+
// src/ui/sections/config.ts
|
|
8
|
+
import { writeFileSync } from "fs";
|
|
9
|
+
import { spawnSync } from "child_process";
|
|
10
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
11
|
+
import { stringify as yamlStringify } from "yaml";
|
|
12
|
+
function getEditableFields() {
|
|
13
|
+
return [
|
|
14
|
+
{ field: "steps", type: "array", description: "Workflow steps (ordered list)" },
|
|
15
|
+
{ field: "output.path", type: "string", description: "Output directory path" },
|
|
16
|
+
{ field: "knowledge.enabled", type: "boolean", description: "Enable knowledge saving" },
|
|
17
|
+
{ field: "knowledge.inject", type: "boolean", description: "Inject knowledge into prompts" },
|
|
18
|
+
{ field: "github.owner", type: "string", description: "GitHub repository owner" },
|
|
19
|
+
{ field: "github.repo", type: "string", description: "GitHub repository name" }
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
function renderConfigJSON(cwd = process.cwd()) {
|
|
23
|
+
const config = loadConfig(cwd);
|
|
24
|
+
const response = {
|
|
25
|
+
section: "config",
|
|
26
|
+
data: config,
|
|
27
|
+
editableFields: getEditableFields(),
|
|
28
|
+
actions: ["set", "edit"]
|
|
29
|
+
};
|
|
30
|
+
return JSON.stringify(response, null, 2);
|
|
31
|
+
}
|
|
32
|
+
function setConfigField(field, value, cwd = process.cwd()) {
|
|
33
|
+
try {
|
|
34
|
+
const configPath = getConfigPath(cwd);
|
|
35
|
+
const config = loadConfig(cwd);
|
|
36
|
+
let parsedValue;
|
|
37
|
+
try {
|
|
38
|
+
parsedValue = JSON.parse(value);
|
|
39
|
+
} catch {
|
|
40
|
+
parsedValue = value;
|
|
41
|
+
}
|
|
42
|
+
const parts = field.split(".");
|
|
43
|
+
let current = config;
|
|
44
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
45
|
+
const part = parts[i];
|
|
46
|
+
if (!(part in current) || typeof current[part] !== "object") {
|
|
47
|
+
current[part] = {};
|
|
48
|
+
}
|
|
49
|
+
current = current[part];
|
|
50
|
+
}
|
|
51
|
+
current[parts[parts.length - 1]] = parsedValue;
|
|
52
|
+
const yamlContent = yamlStringify(config);
|
|
53
|
+
writeFileSync(configPath, yamlContent);
|
|
54
|
+
clearConfigCache();
|
|
55
|
+
return { success: true, message: `Set ${field} = ${JSON.stringify(parsedValue)}` };
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return { success: false, message: `Failed to set ${field}: ${error.message}` };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function openConfigInEditor(cwd = process.cwd()) {
|
|
61
|
+
const configPath = getConfigPath(cwd);
|
|
62
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
63
|
+
const result = spawnSync(editor, [configPath], {
|
|
64
|
+
stdio: "inherit",
|
|
65
|
+
shell: true
|
|
66
|
+
});
|
|
67
|
+
if (result.status === 0) {
|
|
68
|
+
clearConfigCache();
|
|
69
|
+
return { success: true, message: "Config edited successfully" };
|
|
70
|
+
} else {
|
|
71
|
+
return { success: false, message: `Editor exited with code ${result.status}` };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function runConfigInteractive(cwd = process.cwd()) {
|
|
75
|
+
while (true) {
|
|
76
|
+
const config = loadConfig(cwd);
|
|
77
|
+
const action = await select({
|
|
78
|
+
message: "Config Management",
|
|
79
|
+
choices: [
|
|
80
|
+
{ value: "view", name: "\u{1F4CB} View current config" },
|
|
81
|
+
{ value: "steps", name: "\u{1F4DD} Edit steps" },
|
|
82
|
+
{ value: "output", name: "\u{1F4C1} Edit output path" },
|
|
83
|
+
{ value: "knowledge", name: "\u{1F9E0} Edit knowledge settings" },
|
|
84
|
+
{ value: "github", name: "\u{1F419} Edit GitHub settings" },
|
|
85
|
+
{ value: "edit", name: "\u270F\uFE0F Open in editor" },
|
|
86
|
+
{ value: "back", name: "\u2190 Back to main menu" }
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
if (action === "back") {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
if (action === "view") {
|
|
93
|
+
console.log("\nCurrent Config:");
|
|
94
|
+
console.log(JSON.stringify(config, null, 2));
|
|
95
|
+
console.log();
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (action === "edit") {
|
|
99
|
+
const result = openConfigInEditor(cwd);
|
|
100
|
+
console.log(result.message);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (action === "steps") {
|
|
104
|
+
const currentSteps = config.steps.join(", ");
|
|
105
|
+
const newSteps = await input({
|
|
106
|
+
message: "Enter steps (comma-separated):",
|
|
107
|
+
default: currentSteps
|
|
108
|
+
});
|
|
109
|
+
const stepsArray = newSteps.split(",").map((s) => s.trim()).filter(Boolean);
|
|
110
|
+
const result = setConfigField("steps", JSON.stringify(stepsArray), cwd);
|
|
111
|
+
console.log(result.message);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (action === "output") {
|
|
115
|
+
const currentPath = config.output?.path || ".spets/outputs";
|
|
116
|
+
const newPath = await input({
|
|
117
|
+
message: "Output directory path:",
|
|
118
|
+
default: currentPath
|
|
119
|
+
});
|
|
120
|
+
const result = setConfigField("output.path", JSON.stringify(newPath), cwd);
|
|
121
|
+
console.log(result.message);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (action === "knowledge") {
|
|
125
|
+
const enabled = await confirm({
|
|
126
|
+
message: "Enable knowledge saving?",
|
|
127
|
+
default: config.knowledge?.enabled ?? true
|
|
128
|
+
});
|
|
129
|
+
setConfigField("knowledge.enabled", JSON.stringify(enabled), cwd);
|
|
130
|
+
const inject = await confirm({
|
|
131
|
+
message: "Inject knowledge into prompts?",
|
|
132
|
+
default: config.knowledge?.inject ?? true
|
|
133
|
+
});
|
|
134
|
+
const result = setConfigField("knowledge.inject", JSON.stringify(inject), cwd);
|
|
135
|
+
console.log(result.message);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (action === "github") {
|
|
139
|
+
const owner = await input({
|
|
140
|
+
message: "GitHub owner:",
|
|
141
|
+
default: config.github?.owner || ""
|
|
142
|
+
});
|
|
143
|
+
if (owner) {
|
|
144
|
+
setConfigField("github.owner", JSON.stringify(owner), cwd);
|
|
145
|
+
}
|
|
146
|
+
const repo = await input({
|
|
147
|
+
message: "GitHub repo:",
|
|
148
|
+
default: config.github?.repo || ""
|
|
149
|
+
});
|
|
150
|
+
if (repo) {
|
|
151
|
+
const result = setConfigField("github.repo", JSON.stringify(repo), cwd);
|
|
152
|
+
console.log(result.message);
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export {
|
|
160
|
+
renderConfigJSON,
|
|
161
|
+
setConfigField,
|
|
162
|
+
openConfigInEditor,
|
|
163
|
+
runConfigInteractive
|
|
164
|
+
};
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getOutputsDir,
|
|
3
|
+
loadConfig,
|
|
4
|
+
spetsExists
|
|
5
|
+
} from "./chunk-VQV22N3Y.js";
|
|
6
|
+
|
|
7
|
+
// src/ui/sections/tasks.ts
|
|
8
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
9
|
+
|
|
10
|
+
// src/core/state.ts
|
|
11
|
+
import { readFileSync, existsSync, readdirSync, writeFileSync, mkdirSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import matter from "gray-matter";
|
|
14
|
+
|
|
15
|
+
// src/core/slug.ts
|
|
16
|
+
function generateRandomSuffix() {
|
|
17
|
+
return Math.random().toString(36).substring(2, 6);
|
|
18
|
+
}
|
|
19
|
+
function generateDatePrefix() {
|
|
20
|
+
const now = /* @__PURE__ */ new Date();
|
|
21
|
+
const year = now.getFullYear();
|
|
22
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
23
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
24
|
+
return `${year}-${month}-${day}`;
|
|
25
|
+
}
|
|
26
|
+
function sanitizeDescription(description) {
|
|
27
|
+
let sanitized = description.toLowerCase();
|
|
28
|
+
sanitized = sanitized.replace(/[^\x00-\x7F]/g, "");
|
|
29
|
+
sanitized = sanitized.replace(/[^a-z0-9\s]/g, "");
|
|
30
|
+
sanitized = sanitized.trim();
|
|
31
|
+
sanitized = sanitized.replace(/\s+/g, " ");
|
|
32
|
+
sanitized = sanitized.replace(/\s+/g, "-");
|
|
33
|
+
if (sanitized.length > 10) {
|
|
34
|
+
sanitized = sanitized.substring(0, 10);
|
|
35
|
+
}
|
|
36
|
+
sanitized = sanitized.replace(/-+$/, "");
|
|
37
|
+
if (sanitized === "") {
|
|
38
|
+
sanitized = "task";
|
|
39
|
+
}
|
|
40
|
+
return sanitized;
|
|
41
|
+
}
|
|
42
|
+
function generateSlug(description) {
|
|
43
|
+
const datePrefix = generateDatePrefix();
|
|
44
|
+
const meaningfulName = sanitizeDescription(description);
|
|
45
|
+
const randomSuffix = generateRandomSuffix();
|
|
46
|
+
return `${datePrefix}-${meaningfulName}-${randomSuffix}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/core/state.ts
|
|
50
|
+
function generateTaskId(description) {
|
|
51
|
+
if (description) {
|
|
52
|
+
return generateSlug(description);
|
|
53
|
+
}
|
|
54
|
+
const timestamp = Date.now().toString(36);
|
|
55
|
+
const random = Math.random().toString(36).substring(2, 6);
|
|
56
|
+
return `${timestamp}-${random}`;
|
|
57
|
+
}
|
|
58
|
+
function getTaskDir(taskId, cwd = process.cwd()) {
|
|
59
|
+
return join(getOutputsDir(cwd), taskId);
|
|
60
|
+
}
|
|
61
|
+
function getOutputPath(taskId, stepName, cwd = process.cwd()) {
|
|
62
|
+
return join(getTaskDir(taskId, cwd), `${stepName}.md`);
|
|
63
|
+
}
|
|
64
|
+
function loadDocument(taskId, stepName, cwd = process.cwd()) {
|
|
65
|
+
const outputPath = getOutputPath(taskId, stepName, cwd);
|
|
66
|
+
if (!existsSync(outputPath)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const raw = readFileSync(outputPath, "utf-8");
|
|
70
|
+
const { content, data } = matter(raw);
|
|
71
|
+
return {
|
|
72
|
+
content,
|
|
73
|
+
frontmatter: data
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function listTasks(cwd = process.cwd()) {
|
|
77
|
+
const outputsDir = getOutputsDir(cwd);
|
|
78
|
+
if (!existsSync(outputsDir)) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
return readdirSync(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
82
|
+
}
|
|
83
|
+
function getWorkflowState(taskId, config, cwd = process.cwd()) {
|
|
84
|
+
const taskDir = getTaskDir(taskId, cwd);
|
|
85
|
+
if (!existsSync(taskDir)) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const outputs = /* @__PURE__ */ new Map();
|
|
89
|
+
let lastApprovedIndex = -1;
|
|
90
|
+
let currentStatus = "in_progress";
|
|
91
|
+
let userQuery = "";
|
|
92
|
+
for (let i = 0; i < config.steps.length; i++) {
|
|
93
|
+
const stepName = config.steps[i];
|
|
94
|
+
const doc = loadDocument(taskId, stepName, cwd);
|
|
95
|
+
if (doc) {
|
|
96
|
+
outputs.set(stepName, {
|
|
97
|
+
path: getOutputPath(taskId, stepName, cwd),
|
|
98
|
+
status: doc.frontmatter.status
|
|
99
|
+
});
|
|
100
|
+
if (doc.frontmatter.status === "approved") {
|
|
101
|
+
lastApprovedIndex = i;
|
|
102
|
+
} else if (doc.frontmatter.status === "rejected") {
|
|
103
|
+
currentStatus = "rejected";
|
|
104
|
+
}
|
|
105
|
+
if (i === 0 && !userQuery) {
|
|
106
|
+
const queryMatch = doc.content.match(/## User Query\n\n(.+?)(?=\n\n|$)/s);
|
|
107
|
+
if (queryMatch) {
|
|
108
|
+
userQuery = queryMatch[1].trim();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (lastApprovedIndex === config.steps.length - 1) {
|
|
114
|
+
currentStatus = "completed";
|
|
115
|
+
}
|
|
116
|
+
const currentStepIndex = Math.min(lastApprovedIndex + 1, config.steps.length - 1);
|
|
117
|
+
const currentStepName = config.steps[currentStepIndex];
|
|
118
|
+
const currentDoc = loadDocument(taskId, currentStepName, cwd);
|
|
119
|
+
if (currentDoc?.frontmatter.status === "draft" && currentDoc.frontmatter.open_questions?.length) {
|
|
120
|
+
currentStatus = "paused";
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
taskId,
|
|
124
|
+
userQuery,
|
|
125
|
+
currentStepIndex,
|
|
126
|
+
currentStepName,
|
|
127
|
+
status: currentStatus,
|
|
128
|
+
outputs
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function loadTaskMetadata(taskId, cwd = process.cwd()) {
|
|
132
|
+
const metaPath = join(getTaskDir(taskId, cwd), ".meta.json");
|
|
133
|
+
if (!existsSync(metaPath)) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/dashboard/client.ts
|
|
140
|
+
var DashboardClient = class {
|
|
141
|
+
cwd;
|
|
142
|
+
config = null;
|
|
143
|
+
constructor(cwd = process.cwd()) {
|
|
144
|
+
this.cwd = cwd;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Check if spets is initialized in the working directory
|
|
148
|
+
*/
|
|
149
|
+
isInitialized() {
|
|
150
|
+
return spetsExists(this.cwd);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get config, loading if necessary
|
|
154
|
+
*/
|
|
155
|
+
getConfig() {
|
|
156
|
+
if (!this.config) {
|
|
157
|
+
this.config = loadConfig(this.cwd);
|
|
158
|
+
}
|
|
159
|
+
return this.config;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* List all tasks with summary data
|
|
163
|
+
*/
|
|
164
|
+
listTasks(options = {}) {
|
|
165
|
+
const limit = options.limit ?? 20;
|
|
166
|
+
const allTaskIds = listTasks(this.cwd);
|
|
167
|
+
const config = this.getConfig();
|
|
168
|
+
const tasks = [];
|
|
169
|
+
const taskIds = allTaskIds.slice(0, limit);
|
|
170
|
+
for (const taskId of taskIds) {
|
|
171
|
+
const summary = this.getTaskSummary(taskId, config);
|
|
172
|
+
if (summary) {
|
|
173
|
+
tasks.push(summary);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const metadata = {
|
|
177
|
+
totalTasks: allTaskIds.length,
|
|
178
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
179
|
+
truncated: allTaskIds.length > limit,
|
|
180
|
+
limit
|
|
181
|
+
};
|
|
182
|
+
return { tasks, metadata };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get detailed data for a single task
|
|
186
|
+
*/
|
|
187
|
+
getTaskDetail(taskId) {
|
|
188
|
+
const config = this.getConfig();
|
|
189
|
+
const state = getWorkflowState(taskId, config, this.cwd);
|
|
190
|
+
const meta = loadTaskMetadata(taskId, this.cwd);
|
|
191
|
+
if (!state) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const query = meta?.userQuery || state.userQuery || "(no query)";
|
|
195
|
+
const status = this.mapWorkflowStatus(state.status);
|
|
196
|
+
const progress = this.buildProgress(state, config);
|
|
197
|
+
const steps = config.steps.map((stepName, index) => {
|
|
198
|
+
const output = state.outputs.get(stepName);
|
|
199
|
+
const isCurrent = index === state.currentStepIndex;
|
|
200
|
+
let stepStatus = "pending";
|
|
201
|
+
if (output) {
|
|
202
|
+
stepStatus = output.status;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
name: stepName,
|
|
206
|
+
index: index + 1,
|
|
207
|
+
status: stepStatus,
|
|
208
|
+
isCurrent,
|
|
209
|
+
outputPath: output ? getOutputPath(taskId, stepName, this.cwd) : void 0
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
const task = {
|
|
213
|
+
taskId,
|
|
214
|
+
query,
|
|
215
|
+
status,
|
|
216
|
+
progress,
|
|
217
|
+
createdAt: meta?.createdAt || "",
|
|
218
|
+
steps
|
|
219
|
+
};
|
|
220
|
+
return {
|
|
221
|
+
task,
|
|
222
|
+
metadata: {
|
|
223
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Build summary for a single task
|
|
229
|
+
*/
|
|
230
|
+
getTaskSummary(taskId, config) {
|
|
231
|
+
const state = getWorkflowState(taskId, config, this.cwd);
|
|
232
|
+
const meta = loadTaskMetadata(taskId, this.cwd);
|
|
233
|
+
if (!state) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const query = meta?.userQuery || state.userQuery || "(no query)";
|
|
237
|
+
const status = this.mapWorkflowStatus(state.status);
|
|
238
|
+
const progress = this.buildProgress(state, config);
|
|
239
|
+
return {
|
|
240
|
+
taskId,
|
|
241
|
+
query,
|
|
242
|
+
status,
|
|
243
|
+
progress,
|
|
244
|
+
createdAt: meta?.createdAt || ""
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Build progress object from workflow state
|
|
249
|
+
*/
|
|
250
|
+
buildProgress(state, config) {
|
|
251
|
+
const totalSteps = config.steps.length;
|
|
252
|
+
const currentStepIndex = state.currentStepIndex + 1;
|
|
253
|
+
const completionPercent = Math.round(state.currentStepIndex / totalSteps * 100);
|
|
254
|
+
return {
|
|
255
|
+
currentStep: state.currentStepName,
|
|
256
|
+
currentStepIndex,
|
|
257
|
+
totalSteps,
|
|
258
|
+
completionPercent
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Map internal workflow status to dashboard status
|
|
263
|
+
*/
|
|
264
|
+
mapWorkflowStatus(status) {
|
|
265
|
+
switch (status) {
|
|
266
|
+
case "completed":
|
|
267
|
+
return "completed";
|
|
268
|
+
case "rejected":
|
|
269
|
+
return "rejected";
|
|
270
|
+
case "paused":
|
|
271
|
+
return "paused";
|
|
272
|
+
default:
|
|
273
|
+
return "in_progress";
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Export dashboard data as JSON string
|
|
278
|
+
*/
|
|
279
|
+
toJSON(options = {}) {
|
|
280
|
+
const response = this.listTasks(options);
|
|
281
|
+
return JSON.stringify(response, null, 2);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Export task detail as JSON string
|
|
285
|
+
*/
|
|
286
|
+
taskToJSON(taskId) {
|
|
287
|
+
const response = this.getTaskDetail(taskId);
|
|
288
|
+
if (!response) return null;
|
|
289
|
+
return JSON.stringify(response, null, 2);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/dashboard/renderers/cli.ts
|
|
294
|
+
import { select } from "@inquirer/prompts";
|
|
295
|
+
var STATUS_ICONS = {
|
|
296
|
+
in_progress: "\u{1F504}",
|
|
297
|
+
completed: "\u2705",
|
|
298
|
+
paused: "\u23F8\uFE0F",
|
|
299
|
+
rejected: "\u274C"
|
|
300
|
+
};
|
|
301
|
+
var STEP_ICONS = {
|
|
302
|
+
pending: "\u25CB",
|
|
303
|
+
draft: "\u25D0",
|
|
304
|
+
approved: "\u25CF",
|
|
305
|
+
rejected: "\u2717"
|
|
306
|
+
};
|
|
307
|
+
var CLIDashboardRenderer = class {
|
|
308
|
+
/**
|
|
309
|
+
* Render task list in terminal format
|
|
310
|
+
*/
|
|
311
|
+
renderList(response) {
|
|
312
|
+
const { tasks, metadata } = response;
|
|
313
|
+
const lines = [];
|
|
314
|
+
if (tasks.length === 0) {
|
|
315
|
+
lines.push("No tasks found.");
|
|
316
|
+
lines.push("");
|
|
317
|
+
lines.push('Start a new task with: spets start "your task description"');
|
|
318
|
+
return lines.join("\n");
|
|
319
|
+
}
|
|
320
|
+
lines.push("Tasks:");
|
|
321
|
+
lines.push("");
|
|
322
|
+
for (const task of tasks) {
|
|
323
|
+
const icon = STATUS_ICONS[task.status] || "\u2753";
|
|
324
|
+
const progress = `[${task.progress.currentStepIndex}/${task.progress.totalSteps}]`;
|
|
325
|
+
const truncatedQuery = task.query.length > 50 ? task.query.substring(0, 47) + "..." : task.query;
|
|
326
|
+
lines.push(` ${task.taskId} ${icon} ${task.status} ${progress}`);
|
|
327
|
+
lines.push(` ${truncatedQuery}`);
|
|
328
|
+
lines.push("");
|
|
329
|
+
}
|
|
330
|
+
if (metadata.truncated) {
|
|
331
|
+
lines.push(` ... and ${metadata.totalTasks - metadata.limit} more tasks`);
|
|
332
|
+
lines.push("");
|
|
333
|
+
}
|
|
334
|
+
lines.push('Use "spets dashboard -t <taskId>" for details');
|
|
335
|
+
return lines.join("\n");
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Render task detail in terminal format
|
|
339
|
+
*/
|
|
340
|
+
renderDetail(response) {
|
|
341
|
+
const { task } = response;
|
|
342
|
+
const lines = [];
|
|
343
|
+
const icon = STATUS_ICONS[task.status] || "\u2753";
|
|
344
|
+
lines.push(`Task: ${task.taskId}`);
|
|
345
|
+
lines.push(`Query: ${task.query}`);
|
|
346
|
+
lines.push(`Status: ${icon} ${task.status}`);
|
|
347
|
+
lines.push(`Progress: ${task.progress.currentStepIndex}/${task.progress.totalSteps} (${task.progress.completionPercent}%)`);
|
|
348
|
+
lines.push("");
|
|
349
|
+
lines.push("Steps:");
|
|
350
|
+
for (const step of task.steps) {
|
|
351
|
+
const marker = step.isCurrent ? "\u2192" : " ";
|
|
352
|
+
const stepIcon = STEP_ICONS[step.status] || "\u25CB";
|
|
353
|
+
lines.push(` ${marker} ${step.index}. ${step.name} ${stepIcon}`);
|
|
354
|
+
}
|
|
355
|
+
if (task.decisionHistory && task.decisionHistory.length > 0) {
|
|
356
|
+
lines.push("");
|
|
357
|
+
lines.push("Decision History:");
|
|
358
|
+
for (const entry of task.decisionHistory) {
|
|
359
|
+
lines.push(` \u2022 ${entry.decision.decision}: ${entry.answer.selectedOptionId}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (task.verifyOutput) {
|
|
363
|
+
lines.push("");
|
|
364
|
+
lines.push("Verification Scores:");
|
|
365
|
+
const scores = task.verifyOutput.score;
|
|
366
|
+
lines.push(` Requirements: ${scores.requirementsCoverage}%`);
|
|
367
|
+
lines.push(` Template: ${scores.templateCompliance}%`);
|
|
368
|
+
lines.push(` Consistency: ${scores.consistency}%`);
|
|
369
|
+
lines.push(` Completeness: ${scores.completeness}%`);
|
|
370
|
+
}
|
|
371
|
+
return lines.join("\n");
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Run interactive dashboard with drill-down navigation
|
|
375
|
+
*/
|
|
376
|
+
async renderInteractive(client) {
|
|
377
|
+
while (true) {
|
|
378
|
+
const response = client.listTasks({ limit: 10 });
|
|
379
|
+
const { tasks } = response;
|
|
380
|
+
if (tasks.length === 0) {
|
|
381
|
+
console.log("\nNo tasks found.");
|
|
382
|
+
console.log('Start a new task with: spets start "your task description"');
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
const choices = tasks.map((task) => {
|
|
386
|
+
const icon = STATUS_ICONS[task.status] || "\u2753";
|
|
387
|
+
const progress = `[${task.progress.currentStepIndex}/${task.progress.totalSteps}]`;
|
|
388
|
+
const truncatedQuery = task.query.length > 40 ? task.query.substring(0, 37) + "..." : task.query;
|
|
389
|
+
return {
|
|
390
|
+
value: task.taskId,
|
|
391
|
+
name: `${icon} ${task.taskId} ${progress} - ${truncatedQuery}`
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
choices.push({ value: "_exit_", name: "\u2190 Exit dashboard" });
|
|
395
|
+
const selected = await select({
|
|
396
|
+
message: "Select a task to view details:",
|
|
397
|
+
choices
|
|
398
|
+
});
|
|
399
|
+
if (selected === "_exit_") {
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
const detail = client.getTaskDetail(selected);
|
|
403
|
+
if (detail) {
|
|
404
|
+
console.log("\n" + "=".repeat(60));
|
|
405
|
+
console.log(this.renderDetail(detail));
|
|
406
|
+
console.log("=".repeat(60));
|
|
407
|
+
const action = await select({
|
|
408
|
+
message: "What would you like to do?",
|
|
409
|
+
choices: [
|
|
410
|
+
{ value: "back", name: "\u2190 Back to task list" },
|
|
411
|
+
{ value: "resume", name: "\u25B6 Resume this task" },
|
|
412
|
+
{ value: "exit", name: "\u2715 Exit dashboard" }
|
|
413
|
+
]
|
|
414
|
+
});
|
|
415
|
+
if (action === "exit") {
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
if (action === "resume") {
|
|
419
|
+
console.log(`
|
|
420
|
+
To resume this task, run:`);
|
|
421
|
+
console.log(` spets resume -t ${selected}`);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// src/dashboard/renderers/json.ts
|
|
430
|
+
var JSONDashboardRenderer = class {
|
|
431
|
+
prettyPrint;
|
|
432
|
+
constructor(options = {}) {
|
|
433
|
+
this.prettyPrint = options.prettyPrint ?? true;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Render dashboard response as JSON string
|
|
437
|
+
*/
|
|
438
|
+
render(response) {
|
|
439
|
+
return this.stringify(response);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Render task detail as JSON string
|
|
443
|
+
*/
|
|
444
|
+
renderDetail(response) {
|
|
445
|
+
return this.stringify(response);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Render any data as JSON string
|
|
449
|
+
*/
|
|
450
|
+
stringify(data) {
|
|
451
|
+
if (this.prettyPrint) {
|
|
452
|
+
return JSON.stringify(data, null, 2);
|
|
453
|
+
}
|
|
454
|
+
return JSON.stringify(data);
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// src/ui/sections/tasks.ts
|
|
459
|
+
function renderTasksJSON(taskId, cwd = process.cwd()) {
|
|
460
|
+
const client = new DashboardClient(cwd);
|
|
461
|
+
const renderer = new JSONDashboardRenderer();
|
|
462
|
+
if (taskId) {
|
|
463
|
+
const detail = client.getTaskDetail(taskId);
|
|
464
|
+
if (!detail) {
|
|
465
|
+
return JSON.stringify({ error: `Task '${taskId}' not found` });
|
|
466
|
+
}
|
|
467
|
+
return renderer.renderDetail(detail);
|
|
468
|
+
}
|
|
469
|
+
const response = client.listTasks({ limit: 50 });
|
|
470
|
+
return renderer.render(response);
|
|
471
|
+
}
|
|
472
|
+
async function runTasksInteractive(cwd = process.cwd()) {
|
|
473
|
+
const client = new DashboardClient(cwd);
|
|
474
|
+
const renderer = new CLIDashboardRenderer();
|
|
475
|
+
while (true) {
|
|
476
|
+
const response = client.listTasks({ limit: 20 });
|
|
477
|
+
const { tasks } = response;
|
|
478
|
+
if (tasks.length === 0) {
|
|
479
|
+
console.log("\nNo tasks found.");
|
|
480
|
+
console.log('Start a new task with: spets start "your task description"');
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
const STATUS_ICONS2 = {
|
|
484
|
+
in_progress: "\u{1F504}",
|
|
485
|
+
completed: "\u2705",
|
|
486
|
+
paused: "\u23F8\uFE0F",
|
|
487
|
+
rejected: "\u274C"
|
|
488
|
+
};
|
|
489
|
+
const choices = tasks.map((task) => {
|
|
490
|
+
const icon = STATUS_ICONS2[task.status] || "\u2753";
|
|
491
|
+
const progress = `[${task.progress.currentStepIndex}/${task.progress.totalSteps}]`;
|
|
492
|
+
const truncatedQuery = task.query.length > 40 ? task.query.substring(0, 37) + "..." : task.query;
|
|
493
|
+
return {
|
|
494
|
+
value: task.taskId,
|
|
495
|
+
name: `${icon} ${task.taskId} ${progress} - ${truncatedQuery}`
|
|
496
|
+
};
|
|
497
|
+
});
|
|
498
|
+
choices.push({ value: "_back_", name: "\u2190 Back to main menu" });
|
|
499
|
+
const selected = await select2({
|
|
500
|
+
message: "Select a task to view details:",
|
|
501
|
+
choices
|
|
502
|
+
});
|
|
503
|
+
if (selected === "_back_") {
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
const detail = client.getTaskDetail(selected);
|
|
507
|
+
if (detail) {
|
|
508
|
+
console.log("\n" + "=".repeat(60));
|
|
509
|
+
console.log(renderer.renderDetail(detail));
|
|
510
|
+
console.log("=".repeat(60));
|
|
511
|
+
const action = await select2({
|
|
512
|
+
message: "What would you like to do?",
|
|
513
|
+
choices: [
|
|
514
|
+
{ value: "back", name: "\u2190 Back to task list" },
|
|
515
|
+
{ value: "resume", name: "\u25B6 Resume this task" },
|
|
516
|
+
{ value: "exit", name: "\u2715 Exit to main menu" }
|
|
517
|
+
]
|
|
518
|
+
});
|
|
519
|
+
if (action === "exit") {
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
if (action === "resume") {
|
|
523
|
+
console.log(`
|
|
524
|
+
To resume this task, run:`);
|
|
525
|
+
console.log(` spets resume -t ${selected}`);
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export {
|
|
533
|
+
generateTaskId,
|
|
534
|
+
listTasks,
|
|
535
|
+
getWorkflowState,
|
|
536
|
+
loadTaskMetadata,
|
|
537
|
+
DashboardClient,
|
|
538
|
+
JSONDashboardRenderer,
|
|
539
|
+
CLIDashboardRenderer,
|
|
540
|
+
renderTasksJSON,
|
|
541
|
+
runTasksInteractive
|
|
542
|
+
};
|