poe-code 4.0.49 → 4.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-code",
3
- "version": "4.0.49",
3
+ "version": "4.0.51",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,6 +21,9 @@ export declare function editPlan(absolutePath: string, options: {
21
21
  declare function archiveSelectedPlan(entry: Pick<{
22
22
  absolutePath: string;
23
23
  }, "absolutePath">, fs: ActionFs): Promise<string>;
24
+ export declare function unarchivePlan(entry: Pick<{
25
+ absolutePath: string;
26
+ }, "absolutePath">, fs: ActionFs): Promise<string>;
24
27
  export declare function savePlanForLater(entry: Pick<{
25
28
  absolutePath: string;
26
29
  format: PlanFormat;
@@ -102,6 +102,26 @@ async function archiveSelectedPlan(entry, fs) {
102
102
  }
103
103
  return archivedPath;
104
104
  }
105
+ export async function unarchivePlan(entry, fs) {
106
+ rejectPlanMetaDocument(entry.absolutePath, "unarchive");
107
+ const archiveDir = path.dirname(entry.absolutePath);
108
+ if (path.basename(archiveDir) !== "archive") {
109
+ throw new Error(`Plan is not archived: ${entry.absolutePath}`);
110
+ }
111
+ await rejectSymbolicLink(archiveDir, fs, "unarchive plan through");
112
+ const destinationPath = path.join(path.dirname(archiveDir), path.basename(entry.absolutePath));
113
+ try {
114
+ await fs.readFile(destinationPath, "utf8");
115
+ throw new Error(`Unarchive destination already exists: ${destinationPath}`);
116
+ }
117
+ catch (error) {
118
+ if (!hasErrorCode(error, "ENOENT")) {
119
+ throw error;
120
+ }
121
+ }
122
+ await fs.rename(entry.absolutePath, destinationPath);
123
+ return destinationPath;
124
+ }
105
125
  export async function savePlanForLater(entry, fs, options = {}) {
106
126
  const reason = entry.savedForLater?.reason?.trim() || options.reason?.trim();
107
127
  if (!reason) {
@@ -8,6 +8,7 @@ export declare function runPlanBrowser(options: {
8
8
  projectConfigPath: string;
9
9
  fs: DiscoveryFs & Partial<ActionFs>;
10
10
  kind?: PlanKind;
11
+ archived?: boolean;
11
12
  variables?: Record<string, string | undefined>;
12
13
  runExplorerImpl?: RunExplorerImpl;
13
14
  }): Promise<void>;
@@ -9,6 +9,7 @@ export async function runPlanBrowser(options) {
9
9
  projectConfigPath: options.projectConfigPath,
10
10
  fs: options.fs,
11
11
  kind: options.kind,
12
+ archived: options.archived,
12
13
  variables: options.variables
13
14
  });
14
15
  const plans = await discover();
@@ -26,6 +27,7 @@ export async function runPlanBrowser(options) {
26
27
  }
27
28
  const config = buildPlanExplorerConfig({
28
29
  plans,
30
+ archived: options.archived,
29
31
  fs: options.fs,
30
32
  variables: options.variables ?? process.env,
31
33
  homeDir: options.homeDir,
@@ -7,5 +7,6 @@ export declare function discoverAllPlans(options: {
7
7
  configPath: string;
8
8
  projectConfigPath: string;
9
9
  kind?: PlanKind;
10
+ archived?: boolean;
10
11
  variables?: Record<string, string | undefined>;
11
12
  }): Promise<PlanEntry[]>;
@@ -108,7 +108,7 @@ function toPlanKind(value, filePath) {
108
108
  }
109
109
  throw new Error(`${filePath}: unsupported frontmatter kind ${JSON.stringify(value)}`);
110
110
  }
111
- function classifyPlanKind(content, filePath) {
111
+ function classifyPlanKind(content, filePath, archived = false) {
112
112
  if (isYamlPlanFile(filePath)) {
113
113
  parsePlan(content);
114
114
  return "pipeline";
@@ -117,7 +117,19 @@ function classifyPlanKind(content, filePath) {
117
117
  if (data === undefined) {
118
118
  return "plan";
119
119
  }
120
- return data.kind === undefined ? "plan" : toPlanKind(data.kind, filePath);
120
+ if (data.kind === undefined)
121
+ return "plan";
122
+ if (archived && data.kind === "archived-pipeline-plan")
123
+ return "plan";
124
+ if (archived) {
125
+ try {
126
+ return toPlanKind(data.kind, filePath);
127
+ }
128
+ catch {
129
+ return "plan";
130
+ }
131
+ }
132
+ return toPlanKind(data.kind, filePath);
121
133
  }
122
134
  function readPlanReadiness(content, filePath) {
123
135
  const value = splitFrontmatter(content, filePath).data?.readiness;
@@ -142,6 +154,14 @@ async function discoverSharedPlans(options) {
142
154
  if (canonicalDir !== path.resolve(absoluteDir)) {
143
155
  throw new Error(`Plan directory must not be a symbolic link: ${displayDir}`);
144
156
  }
157
+ if (options.archived === true) {
158
+ return discoverPlanDirectoryEntries({
159
+ ...options,
160
+ absoluteDir: path.join(absoluteDir, "archive"),
161
+ displayDir: path.join(displayDir, "archive"),
162
+ savedForLaterDirectory: false
163
+ });
164
+ }
145
165
  const activePlans = await discoverPlanDirectoryEntries({
146
166
  ...options,
147
167
  absoluteDir,
@@ -199,20 +219,35 @@ async function discoverPlanDirectoryEntries(options) {
199
219
  }
200
220
  const displayPath = path.join(options.displayDir, name);
201
221
  const content = await options.fs.readFile(absolutePath, "utf8");
202
- const kind = classifyPlanKind(content, displayPath);
222
+ let kind = classifyPlanKind(content, displayPath, options.archived);
223
+ const savedForLater = options.savedForLaterDirectory
224
+ ? (readSavedForLaterMetadata(content, displayPath) ?? {})
225
+ : readSavedForLaterMetadata(content, displayPath);
226
+ let metadata;
227
+ try {
228
+ metadata = await readPlanMetadata({
229
+ kind,
230
+ absolutePath,
231
+ path: displayPath,
232
+ fs: options.fs,
233
+ content
234
+ });
235
+ }
236
+ catch (error) {
237
+ if (!options.archived || kind === "plan")
238
+ throw error;
239
+ kind = "plan";
240
+ metadata = await readPlanMetadata({
241
+ kind,
242
+ absolutePath,
243
+ path: displayPath,
244
+ fs: options.fs,
245
+ content
246
+ });
247
+ }
203
248
  if (options.kind && kind !== options.kind) {
204
249
  continue;
205
250
  }
206
- const savedForLater = options.savedForLaterDirectory
207
- ? readSavedForLaterMetadata(content, displayPath) ?? {}
208
- : readSavedForLaterMetadata(content, displayPath);
209
- const metadata = await readPlanMetadata({
210
- kind,
211
- absolutePath,
212
- path: displayPath,
213
- fs: options.fs,
214
- content
215
- });
216
251
  plans.push({
217
252
  path: displayPath,
218
253
  absolutePath,
@@ -2,6 +2,7 @@ import type { ExplorerConfig } from "../../toolcraft-design/dist/index.js";
2
2
  import type { ActionFs, DiscoveryFs, PlanEntry } from "./types.js";
3
3
  export interface BuildPlanExplorerConfigOptions {
4
4
  plans: PlanEntry[];
5
+ archived?: boolean;
5
6
  fs: ActionFs & DiscoveryFs;
6
7
  variables: Record<string, string | undefined>;
7
8
  homeDir?: string;
@@ -1,20 +1,20 @@
1
1
  import path from "node:path";
2
2
  import { formatPlanReadinessLabel } from "@poe-code/agent-harness-tools";
3
3
  import { normalizeExplorerConfig } from "toolcraft-design";
4
- import { archivePlan, deletePlan, editFile, restorePlanFromLater, savePlanForLater, setPlanReadiness } from "./actions.js";
4
+ import { archivePlan, deletePlan, editFile, restorePlanFromLater, savePlanForLater, setPlanReadiness, unarchivePlan } from "./actions.js";
5
5
  import { loadPlanPreviewMarkdown } from "./format.js";
6
6
  export function buildPlanExplorerConfig(options) {
7
7
  const loadDetailMarkdown = options.loadDetailMarkdown ?? loadPlanPreviewMarkdown;
8
8
  const promptSaveReason = options.promptSaveReason;
9
9
  let plans = options.plans;
10
- let rows = toRows(plans);
10
+ let rows = toRows(plans, options.archived);
11
11
  let entryByRowId = toEntryMap(plans);
12
12
  let preserveOrderOnNextRefresh = false;
13
13
  async function refresh() {
14
14
  const refreshedPlans = await options.onRefresh();
15
15
  plans = preserveOrderOnNextRefresh ? preservePlanOrder(plans, refreshedPlans) : refreshedPlans;
16
16
  preserveOrderOnNextRefresh = false;
17
- rows = toRows(plans);
17
+ rows = toRows(plans, options.archived);
18
18
  entryByRowId = toEntryMap(plans);
19
19
  }
20
20
  const actions = [
@@ -108,6 +108,22 @@ export function buildPlanExplorerConfig(options) {
108
108
  }
109
109
  }
110
110
  },
111
+ {
112
+ id: "unarchive",
113
+ accelerator: "a",
114
+ label: "Unarchive",
115
+ handler: async (ctx) => {
116
+ const entry = getEntry(entryByRowId, ctx.row.id);
117
+ await unarchivePlan(entry, options.fs);
118
+ try {
119
+ await ctx.refresh();
120
+ ctx.toast(`Unarchived ${path.basename(entry.path)}`, "info");
121
+ }
122
+ catch {
123
+ ctx.toast(`Unarchived ${path.basename(entry.path)}; refresh failed`, "info");
124
+ }
125
+ }
126
+ },
111
127
  {
112
128
  id: "delete",
113
129
  accelerator: "x",
@@ -154,41 +170,49 @@ export function buildPlanExplorerConfig(options) {
154
170
  }
155
171
  ],
156
172
  refresh,
157
- actions,
158
- reorder: {
159
- onReorder: async (orderedIds, ctx) => {
160
- if (ctx === undefined) {
161
- throw new Error("Plan reorder context is required");
162
- }
163
- const orderedEntries = orderedIds.map((id) => getEntry(entryByRowId, id));
164
- const movedIndex = orderedIds.indexOf(ctx.movedId);
165
- const originalIndex = createRowIds(plans).indexOf(ctx.movedId);
166
- if (movedIndex < 0 || originalIndex < 0) {
167
- throw new Error(`Plan row is no longer available: ${ctx.movedId}`);
168
- }
169
- const moved = orderedEntries[movedIndex];
170
- const swapTarget = orderedEntries[originalIndex];
171
- if (swapTarget === undefined || swapTarget.readiness !== moved.readiness) {
172
- throw new Error("Plans can only be reordered within the same readiness group");
173
- }
174
- if (isSavedForLaterEntry(swapTarget) !== isSavedForLaterEntry(moved)) {
175
- throw new Error("Active and saved-for-later plans cannot be reordered together");
173
+ actions: options.archived
174
+ ? actions.filter((action) => action.id === "edit" || action.id === "unarchive" || action.id === "delete")
175
+ : actions.filter((action) => action.id !== "unarchive"),
176
+ ...(options.archived
177
+ ? {}
178
+ : {
179
+ reorder: {
180
+ onReorder: async (orderedIds, ctx) => {
181
+ if (ctx === undefined) {
182
+ throw new Error("Plan reorder context is required");
183
+ }
184
+ const orderedEntries = orderedIds.map((id) => getEntry(entryByRowId, id));
185
+ const movedIndex = orderedIds.indexOf(ctx.movedId);
186
+ const originalIndex = createRowIds(plans).indexOf(ctx.movedId);
187
+ if (movedIndex < 0 || originalIndex < 0) {
188
+ throw new Error(`Plan row is no longer available: ${ctx.movedId}`);
189
+ }
190
+ const moved = orderedEntries[movedIndex];
191
+ const swapTarget = orderedEntries[originalIndex];
192
+ if (swapTarget === undefined || swapTarget.readiness !== moved.readiness) {
193
+ throw new Error("Plans can only be reordered within the same readiness group");
194
+ }
195
+ if (isSavedForLaterEntry(swapTarget) !== isSavedForLaterEntry(moved)) {
196
+ throw new Error("Active and saved-for-later plans cannot be reordered together");
197
+ }
198
+ const previousCandidate = orderedEntries[movedIndex - 1];
199
+ const nextCandidate = orderedEntries[movedIndex + 1];
200
+ const previous = sameOrderingGroup(previousCandidate, moved)
201
+ ? previousCandidate
202
+ : undefined;
203
+ const next = sameOrderingGroup(nextCandidate, moved) ? nextCandidate : undefined;
204
+ const [movedStat, previousStat, nextStat] = await Promise.all([
205
+ options.fs.stat(moved.absolutePath),
206
+ previous === undefined ? undefined : options.fs.stat(previous.absolutePath),
207
+ next === undefined ? undefined : options.fs.stat(next.absolutePath)
208
+ ]);
209
+ const updatedAt = resolveMovedTimestamp(previousStat?.mtimeMs, nextStat?.mtimeMs);
210
+ await options.fs.utimes(moved.absolutePath, new Date(movedStat.atimeMs ?? movedStat.mtimeMs), new Date(updatedAt));
211
+ await ctx.refresh();
212
+ ctx.toast(`Reordered ${path.basename(moved.path)}`, "info");
213
+ }
176
214
  }
177
- const previousCandidate = orderedEntries[movedIndex - 1];
178
- const nextCandidate = orderedEntries[movedIndex + 1];
179
- const previous = sameOrderingGroup(previousCandidate, moved) ? previousCandidate : undefined;
180
- const next = sameOrderingGroup(nextCandidate, moved) ? nextCandidate : undefined;
181
- const [movedStat, previousStat, nextStat] = await Promise.all([
182
- options.fs.stat(moved.absolutePath),
183
- previous === undefined ? undefined : options.fs.stat(previous.absolutePath),
184
- next === undefined ? undefined : options.fs.stat(next.absolutePath)
185
- ]);
186
- const updatedAt = resolveMovedTimestamp(previousStat?.mtimeMs, nextStat?.mtimeMs);
187
- await options.fs.utimes(moved.absolutePath, new Date(movedStat.atimeMs ?? movedStat.mtimeMs), new Date(updatedAt));
188
- await ctx.refresh();
189
- ctx.toast(`Reordered ${path.basename(moved.path)}`, "info");
190
- }
191
- },
215
+ }),
192
216
  multiSelect: false,
193
217
  emptyHint: "No plans found"
194
218
  });
@@ -239,14 +263,14 @@ function abbreviateHome(filePath, homeDir) {
239
263
  }
240
264
  return `~${path.sep}${relative}`;
241
265
  }
242
- function toRows(plans) {
266
+ function toRows(plans, archived = false) {
243
267
  const rowIds = createRowIds(plans);
244
268
  return plans.map((entry, index) => ({
245
269
  id: rowIds[index],
246
270
  title: formatPlanReadinessLabel(path.basename(entry.path), entry.readiness),
247
271
  subtitle: formatSubtitle(entry),
248
272
  badge: { text: entry.typeLabel },
249
- group: isSavedForLaterEntry(entry) ? "Saved for later" : "Active"
273
+ group: archived ? "Archived" : isSavedForLaterEntry(entry) ? "Saved for later" : "Active"
250
274
  }));
251
275
  }
252
276
  function formatSubtitle(entry) {
@@ -1,5 +1,5 @@
1
1
  export { discoverAllPlans } from "./discovery.js";
2
- export { archivePlan, deletePlan, editFile, editPlan, resolveEditor, restorePlanFromLater, savePlanForLater, setPlanReadiness } from "./actions.js";
2
+ export { archivePlan, deletePlan, editFile, editPlan, resolveEditor, restorePlanFromLater, savePlanForLater, setPlanReadiness, unarchivePlan } from "./actions.js";
3
3
  export { buildPlanExplorerConfig } from "./explorer-config.js";
4
4
  export { deriveMarkdownTitle, formatExperimentDetail, formatPipelinePlanMarkdown, formatPipelineProgress, formatRalphDetail, formatSuperintendentDetail, getLastExperimentState, loadPlanPreviewMarkdown, readExperimentState, readPlanMetadata, readSavedForLaterMetadata, writeSavedForLaterReason } from "./format.js";
5
5
  export { runPlanBrowser } from "./browser.js";
@@ -1,5 +1,5 @@
1
1
  export { discoverAllPlans } from "./discovery.js";
2
- export { archivePlan, deletePlan, editFile, editPlan, resolveEditor, restorePlanFromLater, savePlanForLater, setPlanReadiness } from "./actions.js";
2
+ export { archivePlan, deletePlan, editFile, editPlan, resolveEditor, restorePlanFromLater, savePlanForLater, setPlanReadiness, unarchivePlan } from "./actions.js";
3
3
  export { buildPlanExplorerConfig } from "./explorer-config.js";
4
4
  export { deriveMarkdownTitle, formatExperimentDetail, formatPipelinePlanMarkdown, formatPipelineProgress, formatRalphDetail, formatSuperintendentDetail, getLastExperimentState, loadPlanPreviewMarkdown, readExperimentState, readPlanMetadata, readSavedForLaterMetadata, writeSavedForLaterReason } from "./format.js";
5
5
  export { runPlanBrowser } from "./browser.js";