poe-code 3.0.340 → 3.0.342
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/index.js +143 -22
- package/dist/index.js.map +3 -3
- package/dist/metafile.json +1 -1
- package/package.json +1 -1
- package/packages/maestro-tui/dist/actions.js +20 -4
- package/packages/maestro-tui/dist/explorer-config.js +40 -3
- package/packages/plan-browser/dist/actions.js +54 -8
- package/packages/plan-browser/dist/discovery.js +31 -5
package/package.json
CHANGED
|
@@ -5,11 +5,16 @@ export function buildOpenSourceAction(options) {
|
|
|
5
5
|
id: "open-source",
|
|
6
6
|
key: "o",
|
|
7
7
|
label: "Open in $EDITOR",
|
|
8
|
-
predicate: (ctx) => getTask(options.taskByRowId(), ctx.row.id)
|
|
8
|
+
predicate: (ctx) => getSourcePath(getTask(options.taskByRowId(), ctx.row.id)) !== null,
|
|
9
9
|
handler: async (ctx) => {
|
|
10
10
|
const task = getTask(options.taskByRowId(), ctx.row.id);
|
|
11
|
+
const sourcePath = getSourcePath(task);
|
|
12
|
+
if (sourcePath === null) {
|
|
13
|
+
ctx.toast("No source file available.", "info");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
11
16
|
await ctx.suspendAnd(async () => {
|
|
12
|
-
editFile(
|
|
17
|
+
editFile(sourcePath, { env: options.variables });
|
|
13
18
|
});
|
|
14
19
|
await ctx.refresh();
|
|
15
20
|
ctx.toast(`Edited ${task.qualifiedId}`, "info");
|
|
@@ -41,14 +46,25 @@ function getIssueUrl(task) {
|
|
|
41
46
|
if (typeof url !== "string") {
|
|
42
47
|
return null;
|
|
43
48
|
}
|
|
49
|
+
const trimmedUrl = url.trim();
|
|
50
|
+
if (trimmedUrl.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
44
53
|
try {
|
|
45
|
-
const parsedUrl = new URL(
|
|
46
|
-
return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" ?
|
|
54
|
+
const parsedUrl = new URL(trimmedUrl);
|
|
55
|
+
return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" ? trimmedUrl : null;
|
|
47
56
|
}
|
|
48
57
|
catch {
|
|
49
58
|
return null;
|
|
50
59
|
}
|
|
51
60
|
}
|
|
61
|
+
function getSourcePath(task) {
|
|
62
|
+
const sourcePath = task.sourcePath;
|
|
63
|
+
if (typeof sourcePath !== "string" || sourcePath.trim().length === 0) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return sourcePath;
|
|
67
|
+
}
|
|
52
68
|
function getTask(taskByRowId, rowId) {
|
|
53
69
|
const task = taskByRowId.get(rowId);
|
|
54
70
|
if (task === undefined) {
|
|
@@ -85,6 +85,7 @@ async function renderTaskDetailMarkdown(task, taskList) {
|
|
|
85
85
|
const eventsMarkdown = await renderEventsMarkdown(task, taskList);
|
|
86
86
|
const description = task.description.length > 0 ? task.description : "_No description._";
|
|
87
87
|
const metadata = stringify(task.metadata).trimEnd();
|
|
88
|
+
const metadataFence = buildBacktickFence(metadata);
|
|
88
89
|
return [
|
|
89
90
|
`# ${task.name}`,
|
|
90
91
|
"",
|
|
@@ -94,9 +95,9 @@ async function renderTaskDetailMarkdown(task, taskList) {
|
|
|
94
95
|
"",
|
|
95
96
|
"## Metadata",
|
|
96
97
|
"",
|
|
97
|
-
|
|
98
|
+
`${metadataFence}yaml`,
|
|
98
99
|
metadata,
|
|
99
|
-
|
|
100
|
+
metadataFence,
|
|
100
101
|
"",
|
|
101
102
|
"## Next",
|
|
102
103
|
"",
|
|
@@ -112,9 +113,45 @@ async function renderEventsMarkdown(task, taskList) {
|
|
|
112
113
|
return events.map((event) => `- ${event}`).join("\n");
|
|
113
114
|
}
|
|
114
115
|
catch (err) {
|
|
115
|
-
return `_Could not load events: ${err
|
|
116
|
+
return `_Could not load events: ${formatThrownValue(err)}_`;
|
|
116
117
|
}
|
|
117
118
|
}
|
|
119
|
+
function buildBacktickFence(content) {
|
|
120
|
+
return "`".repeat(Math.max(3, longestCharacterRun(content, "`") + 1));
|
|
121
|
+
}
|
|
122
|
+
function longestCharacterRun(content, expected) {
|
|
123
|
+
let longest = 0;
|
|
124
|
+
let current = 0;
|
|
125
|
+
for (const character of content) {
|
|
126
|
+
if (character === expected) {
|
|
127
|
+
current += 1;
|
|
128
|
+
longest = Math.max(longest, current);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
current = 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return longest;
|
|
135
|
+
}
|
|
136
|
+
function formatThrownValue(value) {
|
|
137
|
+
if (value instanceof Error && value.message.length > 0) {
|
|
138
|
+
return value.message;
|
|
139
|
+
}
|
|
140
|
+
if (typeof value === "string" && value.length > 0) {
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const json = JSON.stringify(value);
|
|
145
|
+
if (json !== undefined && json.length > 0) {
|
|
146
|
+
return json;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Fall back to String below.
|
|
151
|
+
}
|
|
152
|
+
const text = String(value);
|
|
153
|
+
return text.length > 0 ? text : "unknown error";
|
|
154
|
+
}
|
|
118
155
|
function toTaskMap(tasks) {
|
|
119
156
|
const taskByRowId = new Map();
|
|
120
157
|
for (const task of tasks) {
|
|
@@ -2,9 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { spawnSync as nodeSpawnSync } from "node:child_process";
|
|
3
3
|
import { hasOwnErrorCode } from "./error-codes.js";
|
|
4
4
|
export function resolveEditor(env = process.env) {
|
|
5
|
-
const editor = getOwnEnvValue(env, "EDITOR")?.trim() ||
|
|
6
|
-
getOwnEnvValue(env, "VISUAL")?.trim() ||
|
|
7
|
-
"vi";
|
|
5
|
+
const editor = getOwnEnvValue(env, "VISUAL")?.trim() || getOwnEnvValue(env, "EDITOR")?.trim() || "vi";
|
|
8
6
|
return editor.length > 0 ? editor : "vi";
|
|
9
7
|
}
|
|
10
8
|
export function editFile(absolutePath, options = {}) {
|
|
@@ -66,12 +64,60 @@ function getOwnEnvValue(env, key) {
|
|
|
66
64
|
function hasErrorCode(error, code) {
|
|
67
65
|
return hasOwnErrorCode(error, code);
|
|
68
66
|
}
|
|
67
|
+
function isCommandWhitespace(character) {
|
|
68
|
+
return character === " " || character === "\t" || character === "\n" || character === "\r";
|
|
69
|
+
}
|
|
69
70
|
function parseEditorCommand(command) {
|
|
70
|
-
const parts =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
const parts = [];
|
|
72
|
+
let current = "";
|
|
73
|
+
let quote;
|
|
74
|
+
let escaping = false;
|
|
75
|
+
let hasToken = false;
|
|
76
|
+
for (const character of command) {
|
|
77
|
+
if (escaping) {
|
|
78
|
+
current += character;
|
|
79
|
+
escaping = false;
|
|
80
|
+
hasToken = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (character === "\\") {
|
|
84
|
+
escaping = true;
|
|
85
|
+
hasToken = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (quote !== undefined) {
|
|
89
|
+
if (character === quote) {
|
|
90
|
+
quote = undefined;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
current += character;
|
|
94
|
+
hasToken = true;
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (character === "'" || character === '"') {
|
|
99
|
+
quote = character;
|
|
100
|
+
hasToken = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (isCommandWhitespace(character)) {
|
|
104
|
+
if (hasToken) {
|
|
105
|
+
parts.push(current);
|
|
106
|
+
current = "";
|
|
107
|
+
hasToken = false;
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
current += character;
|
|
112
|
+
hasToken = true;
|
|
113
|
+
}
|
|
114
|
+
if (escaping) {
|
|
115
|
+
current += "\\";
|
|
116
|
+
}
|
|
117
|
+
if (hasToken) {
|
|
118
|
+
parts.push(current);
|
|
119
|
+
}
|
|
120
|
+
return parts;
|
|
75
121
|
}
|
|
76
122
|
export { archiveSelectedPlan as archivePlan };
|
|
77
123
|
export async function deletePlan(entry, fs) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import * as fsPromises from "node:fs/promises";
|
|
3
|
+
import { parsePlan } from "@poe-code/pipeline";
|
|
3
4
|
import { planConfigScope, readMergedDocumentReadonly, resolveScope } from "@poe-code/poe-code-config";
|
|
4
5
|
import { hasOwnErrorCode } from "./error-codes.js";
|
|
5
6
|
import { readPlanMetadata, splitFrontmatter } from "./format.js";
|
|
@@ -42,8 +43,13 @@ function resolveAbsoluteDirectory(dir, cwd, homeDir) {
|
|
|
42
43
|
}
|
|
43
44
|
return path.isAbsolute(dir) ? dir : path.resolve(cwd, dir);
|
|
44
45
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
46
|
+
function isSupportedPlanFile(name) {
|
|
47
|
+
const lowerName = name.toLowerCase();
|
|
48
|
+
return lowerName.endsWith(".md") || lowerName.endsWith(".yaml") || lowerName.endsWith(".yml");
|
|
49
|
+
}
|
|
50
|
+
function isYamlPlanFile(name) {
|
|
51
|
+
const lowerName = name.toLowerCase();
|
|
52
|
+
return lowerName.endsWith(".yaml") || lowerName.endsWith(".yml");
|
|
47
53
|
}
|
|
48
54
|
function getPlanTypeLabel(kind) {
|
|
49
55
|
switch (kind) {
|
|
@@ -92,6 +98,10 @@ function toPlanKind(value, filePath) {
|
|
|
92
98
|
throw new Error(`${filePath}: unsupported frontmatter kind ${JSON.stringify(value)}`);
|
|
93
99
|
}
|
|
94
100
|
function classifyPlanKind(content, filePath) {
|
|
101
|
+
if (isYamlPlanFile(filePath)) {
|
|
102
|
+
parsePlan(content);
|
|
103
|
+
return "pipeline";
|
|
104
|
+
}
|
|
95
105
|
const { data } = splitFrontmatter(content, filePath);
|
|
96
106
|
if (data === undefined) {
|
|
97
107
|
return "plan";
|
|
@@ -128,15 +138,31 @@ async function discoverSharedPlans(options) {
|
|
|
128
138
|
}
|
|
129
139
|
const plans = [];
|
|
130
140
|
for (const name of entries) {
|
|
131
|
-
if (!
|
|
141
|
+
if (!isSupportedPlanFile(name)) {
|
|
132
142
|
continue;
|
|
133
143
|
}
|
|
134
144
|
const absolutePath = path.join(absoluteDir, name);
|
|
135
|
-
const canonicalPath = await options.fs.realpath(absolutePath)
|
|
145
|
+
const canonicalPath = await options.fs.realpath(absolutePath).catch((error) => {
|
|
146
|
+
if (isNotFound(error)) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
});
|
|
151
|
+
if (canonicalPath === undefined) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
136
154
|
if (canonicalPath !== path.resolve(absolutePath)) {
|
|
137
155
|
throw new Error(`Plan file must not be a symbolic link: ${path.join(displayDir, name)}`);
|
|
138
156
|
}
|
|
139
|
-
const stat = await options.fs.stat(absolutePath)
|
|
157
|
+
const stat = await options.fs.stat(absolutePath).catch((error) => {
|
|
158
|
+
if (isNotFound(error)) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
});
|
|
163
|
+
if (stat === undefined) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
140
166
|
if (!stat.isFile()) {
|
|
141
167
|
continue;
|
|
142
168
|
}
|