@docyrus/docyrus 0.0.39 → 0.0.41

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,12 +1,15 @@
1
1
  {
2
2
  "name": "@docyrus/docyrus",
3
- "version": "0.0.39",
3
+ "version": "0.0.41",
4
4
  "private": false,
5
5
  "description": "Docyrus API CLI",
6
6
  "main": "./main.js",
7
7
  "bin": {
8
8
  "docyrus": "main.js"
9
9
  },
10
+ "scripts": {
11
+ "postinstall": "node ./resources/officecli/install.mjs"
12
+ },
10
13
  "dependencies": {
11
14
  "@clack/prompts": "^0.11.0",
12
15
  "@hono/node-server": "^1.14.1",
@@ -0,0 +1,111 @@
1
+ import { existsSync } from "node:fs";
2
+ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const SKIP_ENV_NAME = "DOCYRUS_SKIP_OFFICECLI_POSTINSTALL";
7
+ const scriptDirectoryPath = dirname(fileURLToPath(import.meta.url));
8
+ const packageRootPath = resolve(scriptDirectoryPath, "..", "..");
9
+ const manifestPath = join(scriptDirectoryPath, "manifest.json");
10
+ const bundledRootPath = join(scriptDirectoryPath, "bundled");
11
+
12
+ function resolveOfficeCliAssetName() {
13
+ switch (process.platform) {
14
+ case "darwin":
15
+ if (process.arch === "arm64") {
16
+ return "officecli-mac-arm64";
17
+ }
18
+ if (process.arch === "x64") {
19
+ return "officecli-mac-x64";
20
+ }
21
+ break;
22
+ case "linux": {
23
+ const alpineSuffix = existsSync("/etc/alpine-release") ? "-alpine" : "";
24
+ if (process.arch === "arm64") {
25
+ return `officecli-linux${alpineSuffix}-arm64`;
26
+ }
27
+ if (process.arch === "x64") {
28
+ return `officecli-linux${alpineSuffix}-x64`;
29
+ }
30
+ break;
31
+ }
32
+ case "win32":
33
+ if (process.arch === "arm64") {
34
+ return "officecli-win-arm64.exe";
35
+ }
36
+ if (process.arch === "x64") {
37
+ return "officecli-win-x64.exe";
38
+ }
39
+ break;
40
+ default:
41
+ break;
42
+ }
43
+
44
+ throw new Error(`Unsupported OfficeCLI platform: ${process.platform}/${process.arch}`);
45
+ }
46
+
47
+ async function readManifest() {
48
+ const manifestRaw = await readFile(manifestPath, "utf8");
49
+ const manifest = JSON.parse(manifestRaw);
50
+
51
+ if (!manifest || typeof manifest.version !== "string" || manifest.version.trim().length === 0) {
52
+ throw new Error("OfficeCLI manifest is missing a valid version.");
53
+ }
54
+
55
+ return {
56
+ version: manifest.version.trim(),
57
+ };
58
+ }
59
+
60
+ async function main() {
61
+ if (process.env[SKIP_ENV_NAME] === "1") {
62
+ return;
63
+ }
64
+
65
+ if (existsSync(join(packageRootPath, "project.json"))) {
66
+ return;
67
+ }
68
+
69
+ const { version } = await readManifest();
70
+ const assetName = resolveOfficeCliAssetName();
71
+ const targetPath = join(bundledRootPath, assetName);
72
+
73
+ if (existsSync(targetPath)) {
74
+ return;
75
+ }
76
+
77
+ await mkdir(bundledRootPath, {
78
+ recursive: true,
79
+ mode: 0o755,
80
+ });
81
+
82
+ const tempPath = `${targetPath}.download${process.platform === "win32" ? ".exe" : ""}`;
83
+
84
+ try {
85
+ const response = await fetch(`https://github.com/iOfficeAI/OfficeCLI/releases/download/v${version}/${assetName}`);
86
+ if (!response.ok) {
87
+ throw new Error(`HTTP ${response.status}`);
88
+ }
89
+
90
+ const binary = Buffer.from(await response.arrayBuffer());
91
+ await writeFile(tempPath, binary, {
92
+ mode: process.platform === "win32" ? 0o644 : 0o755,
93
+ });
94
+
95
+ if (process.platform !== "win32") {
96
+ await chmod(tempPath, 0o755);
97
+ }
98
+
99
+ await rename(tempPath, targetPath);
100
+ } catch (error) {
101
+ await rm(tempPath, {
102
+ force: true,
103
+ });
104
+
105
+ const reason = error instanceof Error ? error.message : "Unknown error";
106
+ process.stderr.write(`[docyrus] OfficeCLI postinstall download failed: ${reason}\n`);
107
+ process.exitCode = 0;
108
+ }
109
+ }
110
+
111
+ await main();
@@ -0,0 +1,3 @@
1
+ {
2
+ "version": "1.0.31"
3
+ }
@@ -28,6 +28,7 @@ const PLAN_ANCHOR_TYPE = "plan-anchor";
28
28
  const PLAN_CONFIG_FILE = ".pi/plan-policy.json";
29
29
  const PLAN_WIDGET_KEY = "plan-mode";
30
30
  const PLAN_BRANCH_LABEL = "plan";
31
+ const ARCHITECT_PLAN_ARTIFACT_FILE_NAME = "PLAN.md";
31
32
  const ALLOWED_TODO_ACTIONS = new Set(["list", "list-all", "get"]);
32
33
  const ALL_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh"] satisfies ThinkingLevel[]);
33
34
 
@@ -96,6 +97,12 @@ export interface IReadPlanPolicyResult {
96
97
  error?: string;
97
98
  }
98
99
 
100
+ interface IPlanningCliEnvironment {
101
+ executable: string;
102
+ entryPath: string;
103
+ scope: "local" | "global";
104
+ }
105
+
99
106
  export type IParseResult<T> = { ok: true; value: T } | { ok: false; error: string };
100
107
 
101
108
  let currentPlanState: IPlanSessionState | undefined;
@@ -120,6 +127,41 @@ function expandUserPath(inputPath: string): string {
120
127
  return inputPath;
121
128
  }
122
129
 
130
+ function readPlanningCliEnvironment(env: NodeJS.ProcessEnv = process.env): IPlanningCliEnvironment {
131
+ const executable = env.DOCYRUS_CLI_EXECUTABLE?.trim();
132
+ const entryPath = env.DOCYRUS_CLI_ENTRY?.trim();
133
+ const scope = env.DOCYRUS_CLI_SCOPE?.trim() as "local" | "global" | undefined;
134
+ if (!executable || !entryPath || (scope !== "local" && scope !== "global")) {
135
+ throw new Error("Missing Docyrus CLI runtime env. Expected DOCYRUS_CLI_EXECUTABLE, DOCYRUS_CLI_ENTRY, and DOCYRUS_CLI_SCOPE.");
136
+ }
137
+
138
+ return {
139
+ executable,
140
+ entryPath,
141
+ scope,
142
+ };
143
+ }
144
+
145
+ async function runProjectPlanCliJson<TValue>(
146
+ pi: ExtensionAPI,
147
+ ctx: ExtensionContext,
148
+ args: string[],
149
+ ): Promise<TValue> {
150
+ const environment = readPlanningCliEnvironment();
151
+ const scopedArgs = environment.scope === "global" ? ["-g", ...args] : args;
152
+ const result = await pi.exec(environment.executable, [environment.entryPath, ...scopedArgs, "--json"], {
153
+ cwd: ctx.cwd,
154
+ });
155
+ const stdout = result.stdout?.toString().trim() || "";
156
+ const stderr = result.stderr?.toString().trim() || "";
157
+ const output = stdout || stderr;
158
+ if (result.code !== 0 || !output) {
159
+ throw new Error(output || `Command exited with code ${result.code ?? "unknown"}.`);
160
+ }
161
+
162
+ return JSON.parse(output) as TValue;
163
+ }
164
+
123
165
  function resolveAgentRootPath(): string {
124
166
  const agentDir = process.env.PI_CODING_AGENT_DIR?.trim();
125
167
  return agentDir && agentDir.length > 0 ? expandUserPath(agentDir) : path.join(os.homedir(), ".pi", "agent");
@@ -635,6 +677,28 @@ async function writePlanArtifactFromEvent(event: AgentEndEvent, ctx: ExtensionCo
635
677
  }
636
678
  }
637
679
 
680
+ async function writeArchitectArtifactFromEvent(event: AgentEndEvent, ctx: ExtensionContext): Promise<void> {
681
+ const state = getPlanState(ctx);
682
+ if (!state?.active || !state.artifactPath || state.mode !== "architect") {
683
+ return;
684
+ }
685
+
686
+ const text = extractLastAssistantText(event.messages ?? []);
687
+ if (!text || parseAskUserRequestFromText(text)) {
688
+ return;
689
+ }
690
+
691
+ try {
692
+ await fs.mkdir(state.artifactPath, { recursive: true });
693
+ await fs.writeFile(path.join(state.artifactPath, ARCHITECT_PLAN_ARTIFACT_FILE_NAME), `${text.trim()}\n`, "utf8");
694
+ } catch (error) {
695
+ if (ctx.hasUI) {
696
+ const message = error instanceof Error ? error.message : String(error);
697
+ ctx.ui.notify(`Failed to write architect plan artifact: ${message}`, "error");
698
+ }
699
+ }
700
+ }
701
+
638
702
  export function shouldBlockTodoAction(action: string | undefined): boolean {
639
703
  return !!action && !ALLOWED_TODO_ACTIONS.has(action);
640
704
  }
@@ -973,6 +1037,36 @@ export async function endPlanningWorkflow(pi: ExtensionAPI, ctx: ExtensionComman
973
1037
  setPlanWidget(ctx, undefined);
974
1038
  await restoreSourceModel(pi, ctx, state);
975
1039
 
1040
+ if (state.artifactPath) {
1041
+ try {
1042
+ const syncResult = state.mode === "architect"
1043
+ ? await runProjectPlanCliJson<{ updatedTaskIds?: string[] }>(pi, ctx, [
1044
+ "project-plan",
1045
+ "upsert-from-architect",
1046
+ "--artifactDir",
1047
+ state.artifactPath,
1048
+ ...(state.task ? ["--brief", state.task] : []),
1049
+ ])
1050
+ : await runProjectPlanCliJson<{ updatedTaskIds?: string[] }>(pi, ctx, [
1051
+ "project-plan",
1052
+ "upsert-from-plan",
1053
+ "--artifactPath",
1054
+ state.artifactPath,
1055
+ ...(state.task ? ["--task", state.task] : []),
1056
+ ]);
1057
+
1058
+ if (ctx.hasUI) {
1059
+ const updatedCount = Array.isArray(syncResult.updatedTaskIds) ? syncResult.updatedTaskIds.length : 0;
1060
+ ctx.ui.notify(`Project plan synced from ${state.mode} artifact (${updatedCount} task update${updatedCount === 1 ? "" : "s"}).`, "info");
1061
+ }
1062
+ } catch (error) {
1063
+ if (ctx.hasUI) {
1064
+ const message = error instanceof Error ? error.message : String(error);
1065
+ ctx.ui.notify(`Project plan sync failed after ${state.mode}: ${message}`, "warning");
1066
+ }
1067
+ }
1068
+ }
1069
+
976
1070
  if (ctx.hasUI) {
977
1071
  const artifactSuffix = state.artifactPath ? ` Artifact: ${state.artifactPath}` : "";
978
1072
  ctx.ui.notify(`Planning session ended.${artifactSuffix}`, "info");
@@ -1176,6 +1270,7 @@ export default function planExtension(pi: ExtensionAPI) {
1176
1270
  }
1177
1271
 
1178
1272
  await writePlanArtifactFromEvent(event, ctx);
1273
+ await writeArchitectArtifactFromEvent(event, ctx);
1179
1274
  });
1180
1275
 
1181
1276
  pi.on("session_start", async(_event, ctx) => {