@hienlh/ppm 0.9.80 → 0.9.82
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/.opencode/.env.example +98 -0
- package/.opencode/skills/ads-management/scripts/.env.example +13 -0
- package/.opencode/skills/ai-multimodal/.env.example +230 -0
- package/.opencode/skills/cip-design/.env.example +6 -0
- package/.opencode/skills/devops/.env.example +76 -0
- package/.opencode/skills/docs-seeker/.env.example +15 -0
- package/.opencode/skills/elevenlabs/.env.example +3 -0
- package/.opencode/skills/marketing-dashboard/.env.example +15 -0
- package/.opencode/skills/marketing-dashboard/app/.env.example +2 -0
- package/.opencode/skills/marketing-dashboard/server/.env.example +2 -0
- package/.opencode/skills/mcp-management/scripts/dist/analyze-tools.js +70 -0
- package/.opencode/skills/mcp-management/scripts/dist/cli.js +160 -0
- package/.opencode/skills/mcp-management/scripts/dist/mcp-client.js +183 -0
- package/.opencode/skills/payment-integration/scripts/.env.example +20 -0
- package/.opencode/skills/sequential-thinking/.env.example +8 -0
- package/.repomixignore +22 -0
- package/AGENTS.md +62 -0
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +12 -0
- package/assets/skills/ppm-guide/SKILL.md +61 -0
- package/bun.lock +9 -1
- package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/web/assets/chat-tab-bS86TsT5.js +10 -0
- package/dist/web/assets/{code-editor-BFe-hnpF.js → code-editor-BaNaQ33b.js} +1 -1
- package/dist/web/assets/{database-viewer-BeY2V5QI.js → database-viewer-C5MVw8cJ.js} +1 -1
- package/dist/web/assets/{diff-viewer-D6xzs8PP.js → diff-viewer-CUbFMWVo.js} +1 -1
- package/dist/web/assets/{extension-webview-Cd1XYFXO.js → extension-webview-CwGufYEP.js} +1 -1
- package/dist/web/assets/{git-graph-D2XXpiMQ.js → git-graph-BD7A7MLo.js} +1 -1
- package/dist/web/assets/index-BYXjCNlK.css +2 -0
- package/dist/web/assets/index-CpzkPHOC.js +30 -0
- package/dist/web/assets/keybindings-store-DsaANvBz.js +1 -0
- package/dist/web/assets/markdown-renderer-C19IsITh.js +326 -0
- package/dist/web/assets/{port-forwarding-tab-B5rj_I66.js → port-forwarding-tab-BF79F1iL.js} +1 -1
- package/dist/web/assets/{postgres-viewer-DnlqzOnm.js → postgres-viewer-_nYiO_wp.js} +1 -1
- package/dist/web/assets/{settings-tab-CNZpuPD3.js → settings-tab-C1SQMbSu.js} +1 -1
- package/dist/web/assets/{sql-query-editor-Df2kzbPj.js → sql-query-editor-6OFvxxuN.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-Cj1G70z4.js → sqlite-viewer-SNVYFXvB.js} +1 -1
- package/dist/web/assets/{terminal-tab-Dv9A7Xe2.js → terminal-tab-BJEkmrDt.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-CPfIEo8t.js → use-monaco-theme-r8FzlCWr.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +78 -0
- package/docs/project-changelog.md +29 -0
- package/docs/system-architecture.md +2 -0
- package/package.json +5 -2
- package/release-manifest.json +15784 -0
- package/scripts/check-ppm-dir-usage.sh +21 -0
- package/scripts/generate-ppm-guide.ts +92 -0
- package/src/cli/commands/init.ts +2 -1
- package/src/cli/commands/logs.ts +11 -11
- package/src/cli/commands/report.ts +3 -2
- package/src/cli/commands/restart.ts +22 -23
- package/src/cli/commands/skills-cmd.ts +123 -0
- package/src/cli/commands/status.ts +7 -8
- package/src/cli/commands/stop.ts +18 -19
- package/src/index.ts +3 -0
- package/src/lib/account-crypto.ts +12 -7
- package/src/providers/claude-agent-sdk.ts +42 -11
- package/src/server/index.ts +8 -8
- package/src/server/routes/chat.ts +4 -2
- package/src/server/routes/upgrade.ts +3 -5
- package/src/server/ws/chat.ts +31 -0
- package/src/services/cloud-ws.service.ts +6 -3
- package/src/services/cloud.service.ts +20 -19
- package/src/services/cloudflared.service.ts +13 -13
- package/src/services/config.service.ts +5 -7
- package/src/services/db.service.ts +5 -6
- package/src/services/extension-rpc-handlers.ts +2 -2
- package/src/services/extension.service.ts +9 -12
- package/src/services/ppm-dir.ts +14 -0
- package/src/services/slash-discovery/builtin-commands.ts +53 -0
- package/src/services/slash-discovery/builtin-handlers.ts +65 -0
- package/src/services/slash-discovery/definition-source.ts +27 -0
- package/src/services/slash-discovery/discover-skill-roots.ts +128 -0
- package/src/services/slash-discovery/fuzzy-search.ts +76 -0
- package/src/services/slash-discovery/index.ts +42 -0
- package/src/services/slash-discovery/resolve-overrides.ts +41 -0
- package/src/services/slash-discovery/skill-loader.ts +156 -0
- package/src/services/slash-discovery/types.ts +51 -0
- package/src/services/slash-items.service.ts +4 -182
- package/src/services/supervisor-state.ts +14 -15
- package/src/services/supervisor-stopped-page.ts +2 -4
- package/src/services/supervisor.ts +15 -15
- package/src/services/tunnel.service.ts +22 -5
- package/src/services/upgrade.service.ts +2 -3
- package/src/types/chat.ts +3 -1
- package/src/web/components/chat/chat-history-bar.tsx +2 -15
- package/src/web/components/chat/chat-tab.tsx +5 -2
- package/src/web/components/chat/message-input.tsx +48 -6
- package/src/web/components/chat/message-list.tsx +19 -5
- package/src/web/components/chat/slash-command-picker.tsx +21 -12
- package/src/web/components/layout/mobile-nav.tsx +47 -21
- package/src/web/components/layout/panel-layout.tsx +11 -0
- package/src/web/components/layout/upgrade-banner.tsx +48 -2
- package/src/web/components/shared/markdown-renderer.tsx +5 -2
- package/src/web/hooks/use-chat.ts +33 -1
- package/src/web/main.tsx +1 -0
- package/src/web/stores/panel-store.ts +25 -1
- package/src/web/styles/globals.css +14 -0
- package/dist/web/assets/chat-tab-CmSLt4tg.js +0 -10
- package/dist/web/assets/index-BtwsLrdT.css +0 -2
- package/dist/web/assets/index-D6_wwsL_.js +0 -30
- package/dist/web/assets/keybindings-store-C8ryKudw.js +0 -1
- package/dist/web/assets/markdown-renderer-xYMhd9cE.js +0 -69
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Fail if any service file uses hardcoded homedir + .ppm (should use getPpmDir())
|
|
3
|
+
# Allowed exceptions: ppm-dir.ts itself, autostart, ppmbot, fs-browse, git-dirs, claude-usage, slash-discovery
|
|
4
|
+
|
|
5
|
+
VIOLATIONS=$(grep -rn -E '(resolve|join)\(homedir\(\).*\.ppm' src/ \
|
|
6
|
+
--include='*.ts' \
|
|
7
|
+
| grep -v 'ppm-dir.ts' \
|
|
8
|
+
| grep -v 'autostart-' \
|
|
9
|
+
| grep -v 'ppmbot/' \
|
|
10
|
+
| grep -v 'bot-cmd' \
|
|
11
|
+
| grep -v 'claude-usage' \
|
|
12
|
+
| grep -v 'fs-browse' \
|
|
13
|
+
| grep -v 'git-dirs' \
|
|
14
|
+
| grep -v 'discover-skill-roots')
|
|
15
|
+
|
|
16
|
+
if [ -n "$VIOLATIONS" ]; then
|
|
17
|
+
echo "ERROR: Direct homedir() + .ppm usage found. Use getPpmDir() from src/services/ppm-dir.ts instead:"
|
|
18
|
+
echo "$VIOLATIONS"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
echo "OK: All services use getPpmDir()"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Generate the bundled PPM guide skill from docs/ and CLAUDE.md.
|
|
4
|
+
* Output: assets/skills/ppm-guide/SKILL.md
|
|
5
|
+
*
|
|
6
|
+
* Usage: bun scripts/generate-ppm-guide.ts
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
9
|
+
import { resolve, dirname } from "node:path";
|
|
10
|
+
|
|
11
|
+
const ROOT = resolve(dirname(import.meta.path), "..");
|
|
12
|
+
const OUT_DIR = resolve(ROOT, "assets/skills/ppm-guide");
|
|
13
|
+
const OUT_FILE = resolve(OUT_DIR, "SKILL.md");
|
|
14
|
+
const MAX_LINES = 150;
|
|
15
|
+
|
|
16
|
+
/** Read a file safely, return empty string if missing */
|
|
17
|
+
function read(rel: string): string {
|
|
18
|
+
const p = resolve(ROOT, rel);
|
|
19
|
+
return existsSync(p) ? readFileSync(p, "utf-8") : "";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Extract first N non-empty lines from content */
|
|
23
|
+
function firstLines(content: string, n: number): string {
|
|
24
|
+
return content.split("\n").filter((l) => l.trim()).slice(0, n).join("\n");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Extract a markdown section by heading (## heading) */
|
|
28
|
+
function extractSection(content: string, heading: string, maxLines = 15): string {
|
|
29
|
+
const regex = new RegExp(`^##\\s+${heading}.*$`, "mi");
|
|
30
|
+
const match = content.match(regex);
|
|
31
|
+
if (!match) return "";
|
|
32
|
+
const start = content.indexOf(match[0]) + match[0].length;
|
|
33
|
+
const rest = content.slice(start);
|
|
34
|
+
const nextHeading = rest.search(/^##\s/m);
|
|
35
|
+
const section = nextHeading > 0 ? rest.slice(0, nextHeading) : rest;
|
|
36
|
+
return section.split("\n").slice(0, maxLines).join("\n").trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- Build guide content ---
|
|
40
|
+
const overview = firstLines(read("docs/project-overview-pdr.md"), 8);
|
|
41
|
+
const claudeMd = read("CLAUDE.md");
|
|
42
|
+
const commands = extractSection(claudeMd, "Commands", 12);
|
|
43
|
+
const devConfig = extractSection(claudeMd, "Dev Config", 10);
|
|
44
|
+
const architecture = extractSection(claudeMd, "Architecture", 8);
|
|
45
|
+
const codeStandards = firstLines(read("docs/code-standards.md"), 10);
|
|
46
|
+
|
|
47
|
+
const sections = [
|
|
48
|
+
"---",
|
|
49
|
+
"name: ppm-guide",
|
|
50
|
+
"description: PPM project structure, commands, config, and development workflow reference",
|
|
51
|
+
'argument-hint: "[topic]"',
|
|
52
|
+
"---",
|
|
53
|
+
"",
|
|
54
|
+
"# PPM Guide",
|
|
55
|
+
"",
|
|
56
|
+
"## Overview",
|
|
57
|
+
overview,
|
|
58
|
+
"",
|
|
59
|
+
"## CLI Commands",
|
|
60
|
+
commands || "See `ppm --help` for available commands.",
|
|
61
|
+
"",
|
|
62
|
+
"## Dev Config",
|
|
63
|
+
devConfig || "Config stored in SQLite (~/.ppm/ppm.db). Dev uses ~/.ppm/ppm.dev.db on port 8081.",
|
|
64
|
+
"",
|
|
65
|
+
"## Architecture",
|
|
66
|
+
architecture || "Hono (HTTP) + Bun WebSocket backend, React + Vite frontend, Claude Agent SDK for AI.",
|
|
67
|
+
"",
|
|
68
|
+
"## Code Standards",
|
|
69
|
+
codeStandards || "See docs/code-standards.md for full conventions.",
|
|
70
|
+
"",
|
|
71
|
+
"## Slash Commands",
|
|
72
|
+
"Use `/skills` to list all available skills and commands.",
|
|
73
|
+
"Use `/help` for session help, `/status` for context usage, `/compact` to reduce context.",
|
|
74
|
+
"",
|
|
75
|
+
"## Dev Workflow",
|
|
76
|
+
"1. `bun dev:server` — Start backend (port 8081, dev DB)",
|
|
77
|
+
"2. `bun dev:web` — Start Vite frontend (port 5173)",
|
|
78
|
+
"3. `bun test` — Run all tests",
|
|
79
|
+
"4. `bun run typecheck` — TypeScript type checking",
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// Enforce line cap
|
|
83
|
+
let output = sections.join("\n");
|
|
84
|
+
const lines = output.split("\n");
|
|
85
|
+
if (lines.length > MAX_LINES) {
|
|
86
|
+
output = lines.slice(0, MAX_LINES).join("\n") + "\n";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Write output
|
|
90
|
+
if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true });
|
|
91
|
+
writeFileSync(OUT_FILE, output, "utf-8");
|
|
92
|
+
console.log(`✓ Generated ${OUT_FILE} (${output.split("\n").length} lines)`);
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolve, basename } from "node:path";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
+
import { getPpmDir } from "../../services/ppm-dir.ts";
|
|
4
5
|
import { input, confirm, select, password } from "@inquirer/prompts";
|
|
5
6
|
import { configService } from "../../services/config.service.ts";
|
|
6
7
|
import { projectService } from "../../services/project.service.ts";
|
|
@@ -25,7 +26,7 @@ export function hasConfig(): boolean {
|
|
|
25
26
|
const dbConfig = getAllConfig();
|
|
26
27
|
if (Object.keys(dbConfig).length > 0) return true;
|
|
27
28
|
} catch {}
|
|
28
|
-
const globalConfig = resolve(
|
|
29
|
+
const globalConfig = resolve(getPpmDir(), "config.yaml");
|
|
29
30
|
return existsSync(globalConfig);
|
|
30
31
|
}
|
|
31
32
|
|
package/src/cli/commands/logs.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { getPpmDir } from "../../services/ppm-dir.ts";
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const logFile = () => resolve(getPpmDir(), "ppm.log");
|
|
6
6
|
|
|
7
7
|
export async function showLogs(options: { tail?: string; follow?: boolean; clear?: boolean }) {
|
|
8
8
|
if (options.clear) {
|
|
9
9
|
const { writeFileSync } = await import("node:fs");
|
|
10
|
-
writeFileSync(
|
|
10
|
+
writeFileSync(logFile(), "");
|
|
11
11
|
console.log("Logs cleared.");
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
if (!existsSync(
|
|
15
|
+
if (!existsSync(logFile())) {
|
|
16
16
|
console.log("No log file found. Start PPM daemon first.");
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const lines = parseInt(options.tail ?? "50", 10);
|
|
21
|
-
const content = readFileSync(
|
|
21
|
+
const content = readFileSync(logFile(), "utf-8");
|
|
22
22
|
const allLines = content.split("\n");
|
|
23
23
|
const lastN = allLines.slice(-lines).join("\n");
|
|
24
24
|
|
|
@@ -32,14 +32,14 @@ export async function showLogs(options: { tail?: string; follow?: boolean; clear
|
|
|
32
32
|
if (options.follow) {
|
|
33
33
|
// Tail -f behavior
|
|
34
34
|
const { watch } = await import("node:fs");
|
|
35
|
-
let lastSize = statSync(
|
|
35
|
+
let lastSize = statSync(logFile()).size;
|
|
36
36
|
console.log("\n--- Following logs (Ctrl+C to stop) ---\n");
|
|
37
37
|
|
|
38
|
-
watch(
|
|
38
|
+
watch(logFile(), () => {
|
|
39
39
|
try {
|
|
40
|
-
const newSize = statSync(
|
|
40
|
+
const newSize = statSync(logFile()).size;
|
|
41
41
|
if (newSize > lastSize) {
|
|
42
|
-
const fd = Bun.file(
|
|
42
|
+
const fd = Bun.file(logFile());
|
|
43
43
|
fd.slice(lastSize, newSize).text().then((text) => {
|
|
44
44
|
process.stdout.write(text);
|
|
45
45
|
});
|
|
@@ -52,7 +52,7 @@ export async function showLogs(options: { tail?: string; follow?: boolean; clear
|
|
|
52
52
|
|
|
53
53
|
/** Get last N lines of log for bug reports */
|
|
54
54
|
export function getRecentLogs(lines = 30): string {
|
|
55
|
-
if (!existsSync(
|
|
56
|
-
const content = readFileSync(
|
|
55
|
+
if (!existsSync(logFile())) return "(no logs)";
|
|
56
|
+
const content = readFileSync(logFile(), "utf-8");
|
|
57
57
|
return content.split("\n").slice(-lines).join("\n").trim() || "(empty)";
|
|
58
58
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { platform, arch, release } from "node:os";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { getRecentLogs } from "./logs.ts";
|
|
5
|
+
import { getPpmDir } from "../../services/ppm-dir.ts";
|
|
5
6
|
|
|
6
7
|
const REPO = "hienlh/ppm";
|
|
7
8
|
|
|
8
9
|
export async function reportBug() {
|
|
9
10
|
const { VERSION: version } = await import("../../version.ts");
|
|
10
11
|
const logs = getRecentLogs(30);
|
|
11
|
-
const statusFile = resolve(
|
|
12
|
+
const statusFile = resolve(getPpmDir(), "status.json");
|
|
12
13
|
let statusInfo = "(not running)";
|
|
13
14
|
if (existsSync(statusFile)) {
|
|
14
15
|
try { statusInfo = readFileSync(statusFile, "utf-8"); } catch {}
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { readFileSync, writeFileSync, existsSync, openSync, unlinkSync, renameSync } from "node:fs";
|
|
3
|
+
import { getPpmDir } from "../../services/ppm-dir.ts";
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const RESTART_RESULT = resolve(PPM_DIR, ".restart-result");
|
|
5
|
+
const statusFile = () => resolve(getPpmDir(), "status.json");
|
|
6
|
+
const pidFile = () => resolve(getPpmDir(), "ppm.pid");
|
|
7
|
+
const restartingFlag = () => resolve(getPpmDir(), ".restarting");
|
|
8
|
+
const restartResult = () => resolve(getPpmDir(), ".restart-result");
|
|
10
9
|
|
|
11
10
|
/** Restart only the server process, keeping the tunnel alive */
|
|
12
11
|
export async function restartServer(options: { config?: string; force?: boolean }) {
|
|
13
12
|
// Ignore SIGHUP so this process survives when PPM terminal dies
|
|
14
13
|
process.on("SIGHUP", () => {});
|
|
15
14
|
|
|
16
|
-
if (!existsSync(
|
|
15
|
+
if (!existsSync(statusFile())) {
|
|
17
16
|
console.log("No PPM daemon running. Use 'ppm start' instead.");
|
|
18
17
|
process.exit(1);
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
let status: Record<string, unknown>;
|
|
22
21
|
try {
|
|
23
|
-
status = JSON.parse(readFileSync(
|
|
22
|
+
status = JSON.parse(readFileSync(statusFile(), "utf-8"));
|
|
24
23
|
} catch {
|
|
25
24
|
console.log("Corrupt status file. Use 'ppm stop && ppm start' instead.");
|
|
26
25
|
process.exit(1);
|
|
@@ -45,7 +44,7 @@ export async function restartServer(options: { config?: string; force?: boolean
|
|
|
45
44
|
// Stopped state: treat restart as resume (send resume command)
|
|
46
45
|
if (state === "stopped") {
|
|
47
46
|
console.log("\n Server is stopped. Resuming via supervisor...\n");
|
|
48
|
-
const cmdFile = resolve(
|
|
47
|
+
const cmdFile = resolve(getPpmDir(), ".supervisor-cmd");
|
|
49
48
|
writeFileSync(cmdFile, JSON.stringify({ action: "resume" }));
|
|
50
49
|
// Signal supervisor (Windows: polling picks up command file)
|
|
51
50
|
if (process.platform !== "win32") {
|
|
@@ -59,7 +58,7 @@ export async function restartServer(options: { config?: string; force?: boolean
|
|
|
59
58
|
while (Date.now() - rStart < 15_000) {
|
|
60
59
|
await Bun.sleep(500);
|
|
61
60
|
try {
|
|
62
|
-
const newStatus = JSON.parse(readFileSync(
|
|
61
|
+
const newStatus = JSON.parse(readFileSync(statusFile(), "utf-8"));
|
|
63
62
|
if (newStatus.state === "running" && newStatus.pid) {
|
|
64
63
|
console.log(` ✓ Server resumed (PID: ${newStatus.pid})`);
|
|
65
64
|
if (newStatus.shareUrl) console.log(` ➜ Share: ${newStatus.shareUrl}`);
|
|
@@ -85,7 +84,7 @@ export async function restartServer(options: { config?: string; force?: boolean
|
|
|
85
84
|
while (Date.now() - start < 15_000) {
|
|
86
85
|
await Bun.sleep(500);
|
|
87
86
|
try {
|
|
88
|
-
const newStatus = JSON.parse(readFileSync(
|
|
87
|
+
const newStatus = JSON.parse(readFileSync(statusFile(), "utf-8"));
|
|
89
88
|
const newPid = newStatus.pid as number | undefined;
|
|
90
89
|
if (newPid && newPid !== oldServerPid) {
|
|
91
90
|
// Verify it's alive
|
|
@@ -120,10 +119,10 @@ export async function restartServer(options: { config?: string; force?: boolean
|
|
|
120
119
|
const host = status.host as string ?? configService.get("host");
|
|
121
120
|
|
|
122
121
|
// Write restarting flag so tunnel cleanup handler skips killing cloudflared
|
|
123
|
-
writeFileSync(
|
|
122
|
+
writeFileSync(restartingFlag(), "");
|
|
124
123
|
|
|
125
124
|
// Clear previous result
|
|
126
|
-
try { unlinkSync(
|
|
125
|
+
try { unlinkSync(restartResult()); } catch {}
|
|
127
126
|
|
|
128
127
|
// Pre-restart message — user sees this before terminal dies (if running inside PPM)
|
|
129
128
|
console.log("\n Restarting PPM server...");
|
|
@@ -135,14 +134,14 @@ export async function restartServer(options: { config?: string; force?: boolean
|
|
|
135
134
|
const params = JSON.stringify({
|
|
136
135
|
serverPid, port, host, serverScript,
|
|
137
136
|
config: options.config ?? "",
|
|
138
|
-
statusFile:
|
|
139
|
-
pidFile:
|
|
140
|
-
restartingFlag:
|
|
141
|
-
resultFile:
|
|
142
|
-
ppmDir:
|
|
137
|
+
statusFile: statusFile(),
|
|
138
|
+
pidFile: pidFile(),
|
|
139
|
+
restartingFlag: restartingFlag(),
|
|
140
|
+
resultFile: restartResult(),
|
|
141
|
+
ppmDir: getPpmDir(),
|
|
143
142
|
});
|
|
144
143
|
|
|
145
|
-
const workerPath = resolve(
|
|
144
|
+
const workerPath = resolve(getPpmDir(), ".restart-worker.ts");
|
|
146
145
|
writeFileSync(workerPath, `
|
|
147
146
|
import { readFileSync, writeFileSync, openSync, unlinkSync, appendFileSync } from "node:fs";
|
|
148
147
|
import { createServer } from "node:net";
|
|
@@ -299,7 +298,7 @@ main();
|
|
|
299
298
|
`);
|
|
300
299
|
|
|
301
300
|
// Spawn worker as a fully detached process
|
|
302
|
-
const logFile = resolve(
|
|
301
|
+
const logFile = resolve(getPpmDir(), "ppm.log");
|
|
303
302
|
const logFd = openSync(logFile, "a");
|
|
304
303
|
const worker = Bun.spawn({
|
|
305
304
|
cmd: [process.execPath, "run", workerPath],
|
|
@@ -313,15 +312,15 @@ main();
|
|
|
313
312
|
const pollStart = Date.now();
|
|
314
313
|
while (Date.now() - pollStart < 20000) {
|
|
315
314
|
await Bun.sleep(500);
|
|
316
|
-
if (existsSync(
|
|
315
|
+
if (existsSync(restartResult())) {
|
|
317
316
|
try {
|
|
318
|
-
const result = JSON.parse(readFileSync(
|
|
317
|
+
const result = JSON.parse(readFileSync(restartResult(), "utf-8"));
|
|
319
318
|
if (result.ok) {
|
|
320
319
|
console.log(` ✓ ${result.message}`);
|
|
321
320
|
} else {
|
|
322
321
|
console.error(` ✗ ${result.message}`);
|
|
323
322
|
}
|
|
324
|
-
unlinkSync(
|
|
323
|
+
unlinkSync(restartResult());
|
|
325
324
|
} catch {}
|
|
326
325
|
process.exit(0);
|
|
327
326
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
const C = {
|
|
4
|
+
reset: "\x1b[0m",
|
|
5
|
+
bold: "\x1b[1m",
|
|
6
|
+
green: "\x1b[32m",
|
|
7
|
+
yellow: "\x1b[33m",
|
|
8
|
+
cyan: "\x1b[36m",
|
|
9
|
+
dim: "\x1b[2m",
|
|
10
|
+
red: "\x1b[31m",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function registerSkillsCommands(program: Command): void {
|
|
14
|
+
const skills = program
|
|
15
|
+
.command("skills")
|
|
16
|
+
.description("Manage and inspect discovered skills & commands")
|
|
17
|
+
.option("--project <path>", "Project path", process.cwd());
|
|
18
|
+
|
|
19
|
+
// Default action (no subcommand) → list
|
|
20
|
+
skills.action(async (opts) => { await listAction(opts); });
|
|
21
|
+
|
|
22
|
+
skills
|
|
23
|
+
.command("list")
|
|
24
|
+
.description("List all discovered skills and commands")
|
|
25
|
+
.option("--json", "JSON output")
|
|
26
|
+
.option("--project <path>", "Project path", process.cwd())
|
|
27
|
+
.action(async (opts) => { await listAction({ ...skills.opts(), ...opts }); });
|
|
28
|
+
|
|
29
|
+
skills
|
|
30
|
+
.command("search <query>")
|
|
31
|
+
.description("Fuzzy search skills and commands")
|
|
32
|
+
.option("--json", "JSON output")
|
|
33
|
+
.option("--project <path>", "Project path", process.cwd())
|
|
34
|
+
.action(async (query: string, opts) => {
|
|
35
|
+
const merged = { ...skills.opts(), ...opts };
|
|
36
|
+
const { listSlashItems, searchSlashItems } = await import("../../services/slash-discovery/index.ts");
|
|
37
|
+
const items = listSlashItems(merged.project ?? process.cwd());
|
|
38
|
+
const results = searchSlashItems(items, query);
|
|
39
|
+
if (merged.json) { console.log(JSON.stringify(results, null, 2)); return; }
|
|
40
|
+
if (results.length === 0) {
|
|
41
|
+
console.log(`${C.yellow}No matches for "${query}"${C.reset}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
console.log(`\n${C.bold}Search results for "${query}"${C.reset} (${results.length} matches)\n`);
|
|
45
|
+
for (const item of results) {
|
|
46
|
+
const typeLabel = item.type === "builtin" ? `${C.green}builtin${C.reset}` : item.type === "skill" ? `${C.cyan}skill${C.reset}` : `${C.yellow}command${C.reset}`;
|
|
47
|
+
console.log(` ${C.bold}/${item.name}${C.reset} [${typeLabel}] ${C.dim}${item.description || ""}${C.reset}`);
|
|
48
|
+
}
|
|
49
|
+
console.log();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
skills
|
|
53
|
+
.command("info <name>")
|
|
54
|
+
.description("Show detailed info for a specific skill")
|
|
55
|
+
.option("--json", "JSON output")
|
|
56
|
+
.option("--project <path>", "Project path", process.cwd())
|
|
57
|
+
.action(async (name: string, opts) => {
|
|
58
|
+
const merged = { ...skills.opts(), ...opts };
|
|
59
|
+
const { listSlashItemsDetailed } = await import("../../services/slash-discovery/index.ts");
|
|
60
|
+
const result = listSlashItemsDetailed(merged.project ?? process.cwd());
|
|
61
|
+
const item = result.active.find((i) => i.name === name)
|
|
62
|
+
?? result.shadowed.find((i) => i.name === name);
|
|
63
|
+
if (!item) {
|
|
64
|
+
console.error(`${C.red}✗${C.reset} Skill "${name}" not found`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
if (merged.json) { console.log(JSON.stringify(item, null, 2)); return; }
|
|
68
|
+
console.log(`\n${C.bold}/${item.name}${C.reset}`);
|
|
69
|
+
console.log(` Type: ${item.type}`);
|
|
70
|
+
console.log(` Source: ${item.source}`);
|
|
71
|
+
console.log(` Scope: ${item.scope}`);
|
|
72
|
+
console.log(` Path: ${item.filePath || "(built-in)"}`);
|
|
73
|
+
console.log(` Description: ${item.description || "(none)"}`);
|
|
74
|
+
if ("shadowedBy" in item) {
|
|
75
|
+
console.log(` ${C.yellow}Shadowed by:${C.reset} ${(item as any).shadowedBy.source}`);
|
|
76
|
+
}
|
|
77
|
+
console.log();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function listAction(opts: any): Promise<void> {
|
|
82
|
+
const { listSlashItemsDetailed } = await import("../../services/slash-discovery/index.ts");
|
|
83
|
+
const result = listSlashItemsDetailed(opts.project ?? process.cwd());
|
|
84
|
+
|
|
85
|
+
if (opts.json) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
86
|
+
|
|
87
|
+
// Roots section
|
|
88
|
+
if (result.roots.length > 0) {
|
|
89
|
+
console.log(`\n${C.bold}Skill Roots:${C.reset}`);
|
|
90
|
+
for (const root of result.roots) {
|
|
91
|
+
console.log(` ${root.path} ${C.dim}(${root.source})${C.reset}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const skills = result.active.filter((i) => i.type === "skill");
|
|
96
|
+
const commands = result.active.filter((i) => i.type === "command");
|
|
97
|
+
const builtins = result.active.filter((i) => i.type === "builtin");
|
|
98
|
+
console.log(`\n${skills.length} skills, ${commands.length} commands, ${builtins.length} built-in (${result.shadowed.length} shadowed)\n`);
|
|
99
|
+
|
|
100
|
+
// Active items table
|
|
101
|
+
const nonBuiltin = result.active.filter((i) => i.type !== "builtin");
|
|
102
|
+
if (nonBuiltin.length > 0) {
|
|
103
|
+
const nameW = Math.max(4, ...nonBuiltin.map((i) => i.name.length));
|
|
104
|
+
const typeW = 7;
|
|
105
|
+
const srcW = Math.max(6, ...nonBuiltin.map((i) => i.source.length));
|
|
106
|
+
const header = ` ${"Name".padEnd(nameW)} ${"Type".padEnd(typeW)} ${"Source".padEnd(srcW)} Description`;
|
|
107
|
+
console.log(`${C.bold}${header}${C.reset}`);
|
|
108
|
+
console.log(` ${"-".repeat(nameW)} ${"-".repeat(typeW)} ${"-".repeat(srcW)} ${"-".repeat(20)}`);
|
|
109
|
+
for (const item of nonBuiltin) {
|
|
110
|
+
const typeLabel = item.type === "skill" ? "skill" : "command";
|
|
111
|
+
console.log(` ${item.name.padEnd(nameW)} ${typeLabel.padEnd(typeW)} ${item.source.padEnd(srcW)} ${item.description || ""}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Shadowed items
|
|
116
|
+
if (result.shadowed.length > 0) {
|
|
117
|
+
console.log(`\n${C.yellow}Shadowed:${C.reset}`);
|
|
118
|
+
for (const item of result.shadowed) {
|
|
119
|
+
console.log(` ${item.name} [${item.type}] ${item.source} ${C.dim}← shadowed by ${item.shadowedBy.source}${C.reset}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
console.log();
|
|
123
|
+
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { readFileSync, existsSync } from "node:fs";
|
|
4
|
-
|
|
5
|
-
const STATUS_FILE = resolve(homedir(), ".ppm", "status.json");
|
|
6
|
-
const PID_FILE = resolve(homedir(), ".ppm", "ppm.pid");
|
|
3
|
+
import { getPpmDir } from "../../services/ppm-dir.ts";
|
|
7
4
|
|
|
8
5
|
interface DaemonStatus {
|
|
9
6
|
running: boolean;
|
|
@@ -33,9 +30,10 @@ function getDaemonStatus(): DaemonStatus {
|
|
|
33
30
|
state: null, pausedAt: null, pauseReason: null, lastCrashError: null,
|
|
34
31
|
};
|
|
35
32
|
|
|
36
|
-
|
|
33
|
+
const statusFile = resolve(getPpmDir(), "status.json");
|
|
34
|
+
if (existsSync(statusFile)) {
|
|
37
35
|
try {
|
|
38
|
-
const data = JSON.parse(readFileSync(
|
|
36
|
+
const data = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
39
37
|
const pid = data.pid as number;
|
|
40
38
|
const tunnelPid = (data.tunnelPid as number) ?? null;
|
|
41
39
|
const tunnelAlive = tunnelPid ? isAlive(tunnelPid) : false;
|
|
@@ -59,9 +57,10 @@ function getDaemonStatus(): DaemonStatus {
|
|
|
59
57
|
} catch { return dead; }
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
|
|
60
|
+
const pidFile = resolve(getPpmDir(), "ppm.pid");
|
|
61
|
+
if (existsSync(pidFile)) {
|
|
63
62
|
try {
|
|
64
|
-
const pid = parseInt(readFileSync(
|
|
63
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
65
64
|
return { ...dead, running: isAlive(pid), pid };
|
|
66
65
|
} catch { return dead; }
|
|
67
66
|
}
|
package/src/cli/commands/stop.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { readFileSync, writeFileSync, unlinkSync, existsSync } from "node:fs";
|
|
3
|
+
import { getPpmDir } from "../../services/ppm-dir.ts";
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const CMD_FILE = resolve(PPM_DIR, ".supervisor-cmd");
|
|
5
|
+
const pidFile = () => resolve(getPpmDir(), "ppm.pid");
|
|
6
|
+
const statusFile = () => resolve(getPpmDir(), "status.json");
|
|
7
|
+
const cmdFile = () => resolve(getPpmDir(), ".supervisor-cmd");
|
|
9
8
|
|
|
10
9
|
function killPid(pid: number, label: string): boolean {
|
|
11
10
|
try {
|
|
@@ -57,18 +56,18 @@ export async function stopServer(options?: { all?: boolean; kill?: boolean }) {
|
|
|
57
56
|
console.log(" Stopping all PPM and cloudflared processes...\n");
|
|
58
57
|
const cfKilled = killAllByName("cloudflared");
|
|
59
58
|
let killed = 0;
|
|
60
|
-
if (existsSync(
|
|
59
|
+
if (existsSync(statusFile())) {
|
|
61
60
|
try {
|
|
62
|
-
const data = JSON.parse(readFileSync(
|
|
61
|
+
const data = JSON.parse(readFileSync(statusFile(), "utf-8"));
|
|
63
62
|
// Kill supervisor first (cascades to server + tunnel children)
|
|
64
63
|
if (data.supervisorPid) { killPid(data.supervisorPid, "supervisor"); killed++; }
|
|
65
64
|
if (data.pid) { killPid(data.pid, "server"); killed++; }
|
|
66
65
|
if (data.tunnelPid) { killPid(data.tunnelPid, "tunnel"); killed++; }
|
|
67
66
|
} catch {}
|
|
68
67
|
}
|
|
69
|
-
if (existsSync(
|
|
68
|
+
if (existsSync(pidFile())) {
|
|
70
69
|
try {
|
|
71
|
-
const pid = parseInt(readFileSync(
|
|
70
|
+
const pid = parseInt(readFileSync(pidFile(), "utf-8").trim(), 10);
|
|
72
71
|
if (!isNaN(pid)) { killPid(pid, "supervisor/server (pidfile)"); killed++; }
|
|
73
72
|
} catch {}
|
|
74
73
|
}
|
|
@@ -89,8 +88,8 @@ export async function stopServer(options?: { all?: boolean; kill?: boolean }) {
|
|
|
89
88
|
/** Soft stop: write command file + signal supervisor → kills server only */
|
|
90
89
|
async function softStopCmd() {
|
|
91
90
|
let status: Record<string, unknown> | null = null;
|
|
92
|
-
if (existsSync(
|
|
93
|
-
try { status = JSON.parse(readFileSync(
|
|
91
|
+
if (existsSync(statusFile())) {
|
|
92
|
+
try { status = JSON.parse(readFileSync(statusFile(), "utf-8")); } catch {}
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
const supervisorPid = (status?.supervisorPid as number) ?? null;
|
|
@@ -115,7 +114,7 @@ async function softStopCmd() {
|
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
// Write soft stop command file + signal supervisor (Windows: polling picks it up)
|
|
118
|
-
writeFileSync(
|
|
117
|
+
writeFileSync(cmdFile(), JSON.stringify({ action: "soft_stop" }));
|
|
119
118
|
if (process.platform !== "win32") {
|
|
120
119
|
try { process.kill(supervisorPid, "SIGUSR2"); } catch (e) {
|
|
121
120
|
console.error(` Failed to signal supervisor: ${e}`);
|
|
@@ -128,7 +127,7 @@ async function softStopCmd() {
|
|
|
128
127
|
while (Date.now() - start < 5000) {
|
|
129
128
|
await Bun.sleep(500);
|
|
130
129
|
try {
|
|
131
|
-
const data = JSON.parse(readFileSync(
|
|
130
|
+
const data = JSON.parse(readFileSync(statusFile(), "utf-8"));
|
|
132
131
|
if (data.state === "stopped") {
|
|
133
132
|
console.log("PPM server stopped. Supervisor still alive (Cloud WS + tunnel).");
|
|
134
133
|
console.log("Use 'ppm start' to restart or 'ppm stop --kill' to fully shut down.");
|
|
@@ -143,12 +142,12 @@ async function softStopCmd() {
|
|
|
143
142
|
async function hardStop() {
|
|
144
143
|
let status: { pid?: number; tunnelPid?: number; supervisorPid?: number } | null = null;
|
|
145
144
|
|
|
146
|
-
if (existsSync(
|
|
147
|
-
try { status = JSON.parse(readFileSync(
|
|
145
|
+
if (existsSync(statusFile())) {
|
|
146
|
+
try { status = JSON.parse(readFileSync(statusFile(), "utf-8")); } catch {}
|
|
148
147
|
}
|
|
149
148
|
|
|
150
|
-
const pidFromFile = existsSync(
|
|
151
|
-
? parseInt(readFileSync(
|
|
149
|
+
const pidFromFile = existsSync(pidFile())
|
|
150
|
+
? parseInt(readFileSync(pidFile(), "utf-8").trim(), 10)
|
|
152
151
|
: NaN;
|
|
153
152
|
|
|
154
153
|
const supervisorPid = status?.supervisorPid ?? null;
|
|
@@ -191,6 +190,6 @@ async function hardStop() {
|
|
|
191
190
|
}
|
|
192
191
|
|
|
193
192
|
function cleanup() {
|
|
194
|
-
if (existsSync(
|
|
195
|
-
if (existsSync(
|
|
193
|
+
if (existsSync(statusFile())) unlinkSync(statusFile());
|
|
194
|
+
if (existsSync(pidFile())) unlinkSync(pidFile());
|
|
196
195
|
}
|
package/src/index.ts
CHANGED
|
@@ -145,6 +145,9 @@ registerAutoStartCommands(program);
|
|
|
145
145
|
const { registerCloudCommands } = await import("./cli/commands/cloud.ts");
|
|
146
146
|
registerCloudCommands(program);
|
|
147
147
|
|
|
148
|
+
const { registerSkillsCommands } = await import("./cli/commands/skills-cmd.ts");
|
|
149
|
+
registerSkillsCommands(program);
|
|
150
|
+
|
|
148
151
|
const { registerExtCommands } = await import("./cli/commands/ext-cmd.ts");
|
|
149
152
|
registerExtCommands(program);
|
|
150
153
|
|
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { getPpmDir } from "../services/ppm-dir.ts";
|
|
5
5
|
|
|
6
6
|
const ALGO = "aes-256-gcm";
|
|
7
7
|
|
|
8
|
-
let
|
|
8
|
+
let _keyPathOverride: string | null = null;
|
|
9
|
+
|
|
10
|
+
function getKeyPath(): string {
|
|
11
|
+
return _keyPathOverride ?? resolve(getPpmDir(), "account.key");
|
|
12
|
+
}
|
|
9
13
|
|
|
10
14
|
/** Override key path (for tests) */
|
|
11
15
|
export function setKeyPath(path: string): void {
|
|
12
|
-
|
|
16
|
+
_keyPathOverride = path;
|
|
13
17
|
_key = null; // invalidate cached key
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
function loadOrCreateKey(): Buffer {
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
const kp = getKeyPath();
|
|
22
|
+
if (existsSync(kp)) {
|
|
23
|
+
return Buffer.from(readFileSync(kp, "utf-8").trim(), "hex");
|
|
19
24
|
}
|
|
20
25
|
const key = randomBytes(32);
|
|
21
|
-
mkdirSync(resolve(
|
|
22
|
-
writeFileSync(
|
|
26
|
+
mkdirSync(resolve(kp, ".."), { recursive: true });
|
|
27
|
+
writeFileSync(kp, key.toString("hex"), { mode: 0o600 });
|
|
23
28
|
return key;
|
|
24
29
|
}
|
|
25
30
|
|