@hienlh/ppm 0.12.12 → 0.13.2
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/CHANGELOG.md +31 -1
- package/README.md +11 -0
- package/assets/skills/ppm/SKILL.md +74 -0
- package/assets/skills/ppm/references/cli-reference.md +728 -0
- package/assets/skills/ppm/references/common-tasks.md +139 -0
- package/assets/skills/ppm/references/http-api.md +204 -0
- package/dist/web/assets/ai-settings-section-QE6nBNgN.js +1 -0
- package/dist/web/assets/{api-settings-C3T95dWg.js → api-settings-DAk7D-NP.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DvZbltvY.js +1 -0
- package/dist/web/assets/{audio-preview-BkbgGtDH.js → audio-preview--hRMnXRZ.js} +1 -1
- package/dist/web/assets/chat-tab-4kL3DNxf.js +12 -0
- package/dist/web/assets/{code-editor-BtspASkW.js → code-editor-Caq5_BaF.js} +4 -4
- package/dist/web/assets/{conflict-editor-Dgsu6fmj.js → conflict-editor-Dlo25nmt.js} +1 -1
- package/dist/web/assets/{csv-preview-DcWCjQkZ.js → csv-preview-HMSavgBb.js} +1 -1
- package/dist/web/assets/{database-viewer-C85RxdMV.js → database-viewer-DcBl6OkV.js} +2 -2
- package/dist/web/assets/{diff-viewer-2pPy97Tl.js → diff-viewer-CCzPq1o-.js} +1 -1
- package/dist/web/assets/{esm-_CLpyLJ_.js → esm-K1XIK4vc.js} +1 -1
- package/dist/web/assets/{extension-store-BZDZ9QRc.js → extension-store-3yZYn07W.js} +1 -1
- package/dist/web/assets/{extension-webview-U1lMYZ0p.js → extension-webview-D7bGVSEd.js} +1 -1
- package/dist/web/assets/{file-store-4BpOJthN.js → file-store-BrbCNyLm.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-BxhdxFgj.js +1 -0
- package/dist/web/assets/{image-preview-BcT1SbY2.js → image-preview-CfkqnhXJ.js} +1 -1
- package/dist/web/assets/index-BGFG66Gh.js +27 -0
- package/dist/web/assets/index-Bce0weeW.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BwAZ2zd8.js +1 -0
- package/dist/web/assets/{input-2eDVjcRZ.js → input-Dk49gO8E.js} +1 -1
- package/dist/web/assets/{keybindings-store-BOG1yviy.js → keybindings-store-B-zET-0o.js} +1 -1
- package/dist/web/assets/keybindings-store-DaBV6qhz.js +1 -0
- package/dist/web/assets/{markdown-renderer-Dbam_-04.js → markdown-renderer-DyAm7zuA.js} +3 -3
- package/dist/web/assets/packet-RMMSAZCW-tx2n5Qry.js +1 -0
- package/dist/web/assets/{pdf-preview-BmHVGx32.js → pdf-preview-CZPcuy5c.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-D6S2MqVT.js +1 -0
- package/dist/web/assets/plus-51UQ45rf.js +1 -0
- package/dist/web/assets/{port-forwarding-tab-Dkq1upWC.js → port-forwarding-tab-3RNozlZ5.js} +1 -1
- package/dist/web/assets/{postgres-viewer-BgBJAJ9q.js → postgres-viewer-CXJv4TXc.js} +3 -3
- package/dist/web/assets/radar-KQ55EAFF-BviZcL-b.js +1 -0
- package/dist/web/assets/{scroll-area-CdxNNnN-.js → scroll-area-BEllam7_.js} +1 -1
- package/dist/web/assets/{settings-store-CMAssqyb.js → settings-store-BLLR7ed8.js} +2 -2
- package/dist/web/assets/settings-tab-Cnav4g2u.js +1 -0
- package/dist/web/assets/{sql-query-editor-b7zJ8XPp.js → sql-query-editor-CVAnRFbi.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-4lLAz1es.js → sqlite-viewer-C8WUEFhA.js} +1 -1
- package/dist/web/assets/{tab-store-DNBsLdPn.js → tab-store-B3M9hjho.js} +1 -1
- package/dist/web/assets/{terminal-tab-BtnqkN1H.js → terminal-tab-CaEsMxp8.js} +1 -1
- package/dist/web/assets/treemap-KZPCXAKY-CM54VdaB.js +1 -0
- package/dist/web/assets/{use-blob-url-QX-XajU8.js → use-blob-url-e9uTXjv5.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-D68oX3XU.js → use-monaco-theme-BkZDwoVd.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-sQS4C_iL.js → vendor-mermaid-Dx86tuVP.js} +2 -2
- package/dist/web/assets/{video-preview-CkOKvVLt.js → video-preview-Dfz71RGb.js} +1 -1
- package/dist/web/index.html +18 -18
- package/dist/web/sw.js +1 -1
- package/docs/project-changelog.md +15 -1
- package/package.json +3 -3
- package/scripts/generate-ppm-skill.ts +23 -0
- package/scripts/lib/generate-cli-reference.ts +81 -0
- package/scripts/lib/generate-common-tasks.ts +14 -0
- package/scripts/lib/generate-http-api.ts +145 -0
- package/scripts/lib/generate-skill-md.ts +28 -0
- package/scripts/lib/write-output.ts +17 -0
- package/src/cli/commands/export-cmd.ts +85 -0
- package/src/index.ts +167 -153
- package/src/server/index.ts +12 -4
- package/src/services/autostart-generator.ts +3 -1
- package/src/services/autostart-register.ts +17 -0
- package/src/services/sd-notify.ts +27 -0
- package/src/services/skill-export/backup-existing.ts +33 -0
- package/src/services/skill-export/copy-bundled-skill.ts +36 -0
- package/src/services/skill-export/generate-db-schema.ts +66 -0
- package/src/services/skill-export/index.ts +6 -0
- package/src/services/skill-export/resolve-assets-dir.ts +31 -0
- package/src/services/skill-export/resolve-target-dir.ts +17 -0
- package/src/services/supervisor.ts +31 -5
- package/src/web/components/chat/chat-history-bar.tsx +2 -9
- package/src/web/components/chat/chat-history-panel.tsx +2 -9
- package/src/web/components/chat/chat-tab.tsx +6 -1
- package/src/web/components/chat/chat-welcome.tsx +1 -18
- package/src/web/components/chat/message-list.tsx +96 -43
- package/src/web/components/layout/draggable-tab.tsx +12 -5
- package/src/web/hooks/use-chat.ts +37 -1
- package/src/web/hooks/use-notification-badge.ts +7 -7
- package/src/web/lib/favicon.ts +37 -15
- package/src/web/lib/flatten-expansions.ts +36 -0
- package/src/web/lib/format-date.ts +21 -0
- package/src/web/styles/globals.css +12 -0
- package/templates/skill/SKILL.md.tmpl +74 -0
- package/templates/skill/common-tasks.md +139 -0
- package/assets/skills/ppm-guide/SKILL.md +0 -61
- package/bun.lock +0 -2062
- package/bunfig.toml +0 -2
- package/dist/web/assets/ai-settings-section-NNWp6nw7.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-DDuzYaUV.js +0 -1
- package/dist/web/assets/chat-tab-BZlP1qjX.js +0 -12
- package/dist/web/assets/chevron-up-BWBvMZkp.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-BURAevTc.js +0 -1
- package/dist/web/assets/index-BWSRKVZn.js +0 -23
- package/dist/web/assets/index-b6tIZImC.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-tSD4Fpi3.js +0 -1
- package/dist/web/assets/keybindings-store-BvdUoEC7.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-DmDLZUrV.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-w03Pc9ZR.js +0 -1
- package/dist/web/assets/pre-compact-button-Dp7Hs49L.js +0 -1
- package/dist/web/assets/pre-compact-section-DnM5fGSR.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-C9XQvoey.js +0 -1
- package/dist/web/assets/settings-tab-zYWKTq5z.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-lmftxSky.js +0 -1
- package/scripts/generate-ppm-guide.ts +0 -92
- package/src/web/components/chat/pre-compact-section.tsx +0 -69
- /package/dist/web/assets/{api-client-DIhJ5qVW.js → api-client-Dvzcc_EO.js} +0 -0
- /package/dist/web/assets/{csv-parser-B5QW8pZ6.js → csv-parser--2WJNgS7.js} +0 -0
- /package/dist/web/assets/{dist-GtkSekuX.js → dist-im4ynINo.js} +0 -0
- /package/dist/web/assets/{katex-C3cZrCvP.js → katex-CKoArbIw.js} +0 -0
- /package/dist/web/assets/{lib-Bu71-TFS.js → lib-DQHnkzGy.js} +0 -0
- /package/dist/web/assets/{react-DMIOAtcX.js → react-GqWghJ-L.js} +0 -0
- /package/dist/web/assets/{refresh-cw-BjrAbUJe.js → refresh-cw-LlbZDJpO.js} +0 -0
- /package/dist/web/assets/{sql-completion-provider-CULTsCqR.js → sql-completion-provider-C3cq9j99.js} +0 -0
- /package/dist/web/assets/{table-tf7pRkME.js → table-Dq575bPF.js} +0 -0
- /package/dist/web/assets/{text-wrap-BV-R4Vvy.js → text-wrap-Cn6BNQfq.js} +0 -0
- /package/dist/web/assets/{trash-2-DjQOpgUV.js → trash-2-CJYoLw7Q.js} +0 -0
- /package/dist/web/assets/{utils-CQux7CsO.js → utils-CTg5uAYR.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-K3_Xwigj.js → vendor-xterm-CU2c3f0A.js} +0 -0
package/src/index.ts
CHANGED
|
@@ -3,156 +3,170 @@
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { VERSION } from "./version.ts";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
program
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
program
|
|
6
|
+
/**
|
|
7
|
+
* Assemble the CLI program without parsing argv. Exported so build-time tools
|
|
8
|
+
* (e.g. scripts/generate-ppm-skill.ts) can introspect the Commander tree for
|
|
9
|
+
* auto-generated documentation. `preAction` hooks and action callbacks are
|
|
10
|
+
* registered but not invoked until `.parseAsync()` runs.
|
|
11
|
+
*/
|
|
12
|
+
export async function buildProgram(): Promise<Command> {
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name("ppm")
|
|
17
|
+
.description("Personal Project Manager — mobile-first web IDE")
|
|
18
|
+
.version(VERSION)
|
|
19
|
+
.hook("preAction", () => {
|
|
20
|
+
console.log(` PPM v${VERSION}\n`);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command("start")
|
|
25
|
+
.description("Start the PPM server (background by default)")
|
|
26
|
+
.option("-p, --port <port>", "Port to listen on")
|
|
27
|
+
.option("-s, --share", "(deprecated) Tunnel is now always enabled")
|
|
28
|
+
.option("--profile <name>", "DB profile name (e.g. 'dev' → ppm.dev.db)")
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
const { setDbProfile } = await import("./services/db.service.ts");
|
|
31
|
+
if (options.profile) {
|
|
32
|
+
setDbProfile(options.profile);
|
|
33
|
+
}
|
|
34
|
+
const { hasConfig, initProject } = await import("./cli/commands/init.ts");
|
|
35
|
+
if (!hasConfig()) {
|
|
36
|
+
await initProject();
|
|
37
|
+
}
|
|
38
|
+
const { startServer } = await import("./server/index.ts");
|
|
39
|
+
await startServer(options);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
program
|
|
43
|
+
.command("stop")
|
|
44
|
+
.description("Stop the PPM server (supervisor stays alive)")
|
|
45
|
+
.option("-a, --all", "Kill all PPM and cloudflared processes (including untracked)")
|
|
46
|
+
.option("--kill", "Full shutdown (kills supervisor too)")
|
|
47
|
+
.action(async (options) => {
|
|
48
|
+
const { stopServer } = await import("./cli/commands/stop.ts");
|
|
49
|
+
await stopServer(options);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
program
|
|
53
|
+
.command("down")
|
|
54
|
+
.description("Fully shut down PPM (supervisor + server + tunnel)")
|
|
55
|
+
.action(async () => {
|
|
56
|
+
const { stopServer } = await import("./cli/commands/stop.ts");
|
|
57
|
+
await stopServer({ kill: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
program
|
|
61
|
+
.command("restart")
|
|
62
|
+
.description("Restart the server (keeps tunnel alive)")
|
|
63
|
+
.option("--force", "Force resume from paused state")
|
|
64
|
+
.action(async (options) => {
|
|
65
|
+
const { restartServer } = await import("./cli/commands/restart.ts");
|
|
66
|
+
await restartServer(options);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
program
|
|
70
|
+
.command("status")
|
|
71
|
+
.description("Show PPM daemon status")
|
|
72
|
+
.option("-a, --all", "Show all PPM and cloudflared processes (including untracked)")
|
|
73
|
+
.option("--json", "Output as JSON")
|
|
74
|
+
.action(async (options) => {
|
|
75
|
+
const { showStatus } = await import("./cli/commands/status.ts");
|
|
76
|
+
await showStatus(options);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
program
|
|
80
|
+
.command("open")
|
|
81
|
+
.description("Open PPM in browser")
|
|
82
|
+
.action(async () => {
|
|
83
|
+
const { openBrowser } = await import("./cli/commands/open.ts");
|
|
84
|
+
await openBrowser();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
program
|
|
88
|
+
.command("logs")
|
|
89
|
+
.description("View PPM daemon logs")
|
|
90
|
+
.option("-n, --tail <lines>", "Number of lines to show", "50")
|
|
91
|
+
.option("-f, --follow", "Follow log output")
|
|
92
|
+
.option("--clear", "Clear log file")
|
|
93
|
+
.action(async (options) => {
|
|
94
|
+
const { showLogs } = await import("./cli/commands/logs.ts");
|
|
95
|
+
await showLogs(options);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
program
|
|
99
|
+
.command("report")
|
|
100
|
+
.description("Report a bug on GitHub (pre-fills env info + logs)")
|
|
101
|
+
.action(async () => {
|
|
102
|
+
const { reportBug } = await import("./cli/commands/report.ts");
|
|
103
|
+
await reportBug();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
program
|
|
107
|
+
.command("init")
|
|
108
|
+
.description("Initialize PPM configuration (interactive or via flags)")
|
|
109
|
+
.option("-p, --port <port>", "Port to listen on")
|
|
110
|
+
.option("--scan <path>", "Directory to scan for git repos")
|
|
111
|
+
.option("--auth", "Enable authentication")
|
|
112
|
+
.option("--no-auth", "Disable authentication")
|
|
113
|
+
.option("--password <pw>", "Set access password")
|
|
114
|
+
.option("--share", "Pre-install cloudflared for sharing")
|
|
115
|
+
.option("-y, --yes", "Non-interactive mode (use defaults + flags)")
|
|
116
|
+
.action(async (options) => {
|
|
117
|
+
const { initProject } = await import("./cli/commands/init.ts");
|
|
118
|
+
await initProject(options);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const { registerProjectsCommands } = await import("./cli/commands/projects.ts");
|
|
122
|
+
registerProjectsCommands(program);
|
|
123
|
+
|
|
124
|
+
const { registerConfigCommands } = await import("./cli/commands/config-cmd.ts");
|
|
125
|
+
registerConfigCommands(program);
|
|
126
|
+
|
|
127
|
+
const { registerGitCommands } = await import("./cli/commands/git-cmd.ts");
|
|
128
|
+
registerGitCommands(program);
|
|
129
|
+
|
|
130
|
+
const { registerChatCommands } = await import("./cli/commands/chat-cmd.ts");
|
|
131
|
+
registerChatCommands(program);
|
|
132
|
+
|
|
133
|
+
program
|
|
134
|
+
.command("upgrade")
|
|
135
|
+
.description("Check for and install PPM updates")
|
|
136
|
+
.option("--check", "Only check for updates, don't install")
|
|
137
|
+
.action(async (options) => {
|
|
138
|
+
const { upgradeCmd } = await import("./cli/commands/upgrade.ts");
|
|
139
|
+
await upgradeCmd(options);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const { registerAutoStartCommands } = await import("./cli/commands/autostart.ts");
|
|
143
|
+
registerAutoStartCommands(program);
|
|
144
|
+
|
|
145
|
+
const { registerCloudCommands } = await import("./cli/commands/cloud.ts");
|
|
146
|
+
registerCloudCommands(program);
|
|
147
|
+
|
|
148
|
+
const { registerSkillsCommands } = await import("./cli/commands/skills-cmd.ts");
|
|
149
|
+
registerSkillsCommands(program);
|
|
150
|
+
|
|
151
|
+
const { registerExtCommands } = await import("./cli/commands/ext-cmd.ts");
|
|
152
|
+
registerExtCommands(program);
|
|
153
|
+
|
|
154
|
+
const { registerDbCommands } = await import("./cli/commands/db-cmd.ts");
|
|
155
|
+
registerDbCommands(program);
|
|
156
|
+
|
|
157
|
+
const { registerBotCommands } = await import("./cli/commands/bot-cmd.ts");
|
|
158
|
+
registerBotCommands(program);
|
|
159
|
+
|
|
160
|
+
const { registerJiraCommands } = await import("./cli/commands/jira-cmd.ts");
|
|
161
|
+
await registerJiraCommands(program);
|
|
162
|
+
|
|
163
|
+
const { registerExportCommands } = await import("./cli/commands/export-cmd.ts");
|
|
164
|
+
registerExportCommands(program);
|
|
165
|
+
|
|
166
|
+
return program;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (import.meta.main) {
|
|
170
|
+
const program = await buildProgram();
|
|
171
|
+
program.parse();
|
|
172
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -499,11 +499,15 @@ export async function startServer(options: {
|
|
|
499
499
|
qr.generate(shareUrl, { small: true });
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
-
// Auto-enable system service (systemd/launchd) for boot resilience
|
|
502
|
+
// Auto-enable system service (systemd/launchd) for boot resilience.
|
|
503
|
+
// Also regenerates a stale unit file (missing Type=notify) so users who
|
|
504
|
+
// upgrade past the v0.13 → v0.14 systemd cgroup fix get the new unit
|
|
505
|
+
// picked up on next `systemctl --user restart ppm.service` / reboot.
|
|
503
506
|
try {
|
|
504
|
-
const { getAutoStartStatus, enableAutoStart } = await import("../services/autostart-register.ts");
|
|
507
|
+
const { getAutoStartStatus, enableAutoStart, isAutoStartUnitStale } = await import("../services/autostart-register.ts");
|
|
505
508
|
const status = getAutoStartStatus();
|
|
506
|
-
|
|
509
|
+
const stale = status.enabled && isAutoStartUnitStale();
|
|
510
|
+
if (!status.enabled || stale) {
|
|
507
511
|
const autoConfig = {
|
|
508
512
|
port, host,
|
|
509
513
|
share: !!options.share,
|
|
@@ -511,7 +515,11 @@ export async function startServer(options: {
|
|
|
511
515
|
};
|
|
512
516
|
// skipStart: supervisor is already running from direct spawn above
|
|
513
517
|
await enableAutoStart(autoConfig, { skipStart: true });
|
|
514
|
-
|
|
518
|
+
if (stale) {
|
|
519
|
+
console.log(` ↻ Auto-restart config migrated (Type=notify). Run 'systemctl --user restart ppm.service' to apply.`);
|
|
520
|
+
} else {
|
|
521
|
+
console.log(` ✓ Auto-restart enabled (${status.platform}). Disable: ppm autostart disable`);
|
|
522
|
+
}
|
|
515
523
|
}
|
|
516
524
|
} catch {}
|
|
517
525
|
|
|
@@ -125,10 +125,12 @@ After=network-online.target
|
|
|
125
125
|
Wants=network-online.target
|
|
126
126
|
|
|
127
127
|
[Service]
|
|
128
|
-
Type=
|
|
128
|
+
Type=notify
|
|
129
|
+
NotifyAccess=all
|
|
129
130
|
ExecStart=${execStart}
|
|
130
131
|
Restart=on-failure
|
|
131
132
|
RestartSec=5
|
|
133
|
+
TimeoutStartSec=60
|
|
132
134
|
TimeoutStopSec=10
|
|
133
135
|
KillMode=mixed
|
|
134
136
|
${envPath}
|
|
@@ -350,4 +350,21 @@ export function getAutoStartStatus(): AutoStartStatus {
|
|
|
350
350
|
};
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
/**
|
|
354
|
+
* Detect whether an existing systemd unit file is outdated and needs
|
|
355
|
+
* regeneration. Currently flags units missing Type=notify (introduced to fix
|
|
356
|
+
* the WSL/systemd upgrade-kill bug). Linux-only; returns false elsewhere.
|
|
357
|
+
*/
|
|
358
|
+
export function isAutoStartUnitStale(): boolean {
|
|
359
|
+
if (process.platform !== "linux") return false;
|
|
360
|
+
try {
|
|
361
|
+
const path = getServicePath();
|
|
362
|
+
if (!existsSync(path)) return false;
|
|
363
|
+
const content = readFileSync(path, "utf-8");
|
|
364
|
+
return !content.includes("Type=notify");
|
|
365
|
+
} catch {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
353
370
|
export { loadMetadata, METADATA_FILE };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sd_notify helper — forwards messages to systemd via the `systemd-notify` binary.
|
|
3
|
+
* No-op on non-systemd platforms (NOTIFY_SOCKET unset).
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* await sdNotify("READY=1"); // mark unit active (Type=notify)
|
|
7
|
+
* await sdNotify(`MAINPID=${newPid}`); // handoff main process (NotifyAccess=all)
|
|
8
|
+
*
|
|
9
|
+
* Shelling out to `systemd-notify` avoids implementing AF_UNIX SOCK_DGRAM
|
|
10
|
+
* transport in Node/Bun (not supported by node:dgram). The binary ships with
|
|
11
|
+
* systemd itself, so availability matches systemd availability.
|
|
12
|
+
*/
|
|
13
|
+
export async function sdNotify(state: string): Promise<void> {
|
|
14
|
+
if (!process.env.NOTIFY_SOCKET) return; // not running under systemd
|
|
15
|
+
try {
|
|
16
|
+
const proc = Bun.spawn({
|
|
17
|
+
cmd: ["systemd-notify", state],
|
|
18
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
19
|
+
env: process.env,
|
|
20
|
+
});
|
|
21
|
+
await proc.exited;
|
|
22
|
+
} catch {
|
|
23
|
+
// best-effort: if systemd-notify is missing, startup still proceeds
|
|
24
|
+
// (Type=notify units without READY=1 will time out, but that's already
|
|
25
|
+
// the failure mode — this helper doesn't make it worse).
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Rename existing skill files to `.bak-<timestamp>` before overwriting.
|
|
2
|
+
// Preserves earlier backups by embedding a UTC timestamp in the suffix.
|
|
3
|
+
import { existsSync, renameSync, readdirSync, statSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
/** Produce a compact UTC timestamp like `202604211733` (YYYYMMDDHHmm). */
|
|
7
|
+
export function makeTimestamp(d: Date = new Date()): string {
|
|
8
|
+
return d.toISOString().replace(/[-:T]/g, "").slice(0, 12);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function backupExisting(targetDir: string, ts: string = makeTimestamp()): string[] {
|
|
12
|
+
if (!existsSync(targetDir)) return [];
|
|
13
|
+
const backedUp: string[] = [];
|
|
14
|
+
walkAndBackup(targetDir, ts, backedUp);
|
|
15
|
+
return backedUp;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function walkAndBackup(dir: string, ts: string, collected: string[]): void {
|
|
19
|
+
const entries = readdirSync(dir);
|
|
20
|
+
for (const name of entries) {
|
|
21
|
+
// Skip already-backed-up files to avoid `.md.bak-X.bak-Y` chains.
|
|
22
|
+
if (name.includes(".bak-")) continue;
|
|
23
|
+
const abs = resolve(dir, name);
|
|
24
|
+
const st = statSync(abs);
|
|
25
|
+
if (st.isDirectory()) {
|
|
26
|
+
walkAndBackup(abs, ts, collected);
|
|
27
|
+
} else if (st.isFile() && name.endsWith(".md")) {
|
|
28
|
+
const dest = `${abs}.bak-${ts}`;
|
|
29
|
+
renameSync(abs, dest);
|
|
30
|
+
collected.push(dest);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Recursive copy of the bundled skill package to a target directory.
|
|
2
|
+
// Skips any stale `.bak-*` files in source (defensive, should not occur in bundle).
|
|
3
|
+
import { cpSync, mkdirSync, existsSync, readdirSync, statSync, copyFileSync } from "node:fs";
|
|
4
|
+
import { resolve, relative } from "node:path";
|
|
5
|
+
|
|
6
|
+
export function copyBundledSkill(sourceDir: string, targetDir: string): string[] {
|
|
7
|
+
if (!existsSync(sourceDir)) {
|
|
8
|
+
throw new Error(`Source skill dir not found: ${sourceDir}`);
|
|
9
|
+
}
|
|
10
|
+
mkdirSync(targetDir, { recursive: true });
|
|
11
|
+
const copied: string[] = [];
|
|
12
|
+
walk(sourceDir, sourceDir, targetDir, copied);
|
|
13
|
+
return copied;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function walk(rootSrc: string, dir: string, targetRoot: string, collected: string[]): void {
|
|
17
|
+
for (const name of readdirSync(dir)) {
|
|
18
|
+
if (name.includes(".bak-")) continue;
|
|
19
|
+
const abs = resolve(dir, name);
|
|
20
|
+
const rel = relative(rootSrc, abs);
|
|
21
|
+
const dest = resolve(targetRoot, rel);
|
|
22
|
+
const st = statSync(abs);
|
|
23
|
+
if (st.isDirectory()) {
|
|
24
|
+
mkdirSync(dest, { recursive: true });
|
|
25
|
+
walk(rootSrc, abs, targetRoot, collected);
|
|
26
|
+
} else if (st.isFile()) {
|
|
27
|
+
copyFileSync(abs, dest);
|
|
28
|
+
collected.push(dest);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Fallback single-shot copy using Node 16.7+ cpSync (kept in case walk is bypassed).
|
|
34
|
+
export function copyTree(src: string, dest: string): void {
|
|
35
|
+
cpSync(src, dest, { recursive: true });
|
|
36
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Runtime: read the user's PPM SQLite config DB (readonly) and render a markdown schema doc.
|
|
2
|
+
// Never opens the DB read-write. Gracefully handles missing DB.
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
6
|
+
import { getPpmDir } from "../ppm-dir.ts";
|
|
7
|
+
|
|
8
|
+
interface ColumnInfo {
|
|
9
|
+
cid: number;
|
|
10
|
+
name: string;
|
|
11
|
+
type: string;
|
|
12
|
+
notnull: number;
|
|
13
|
+
dflt_value: string | null;
|
|
14
|
+
pk: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface TableRow {
|
|
18
|
+
name: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function generateDbSchemaMarkdown(dbPath?: string): string {
|
|
22
|
+
const path = dbPath ?? resolve(getPpmDir(), "ppm.db");
|
|
23
|
+
const header = "# PPM Database Schema\n\n_Auto-generated at install time from your local config DB._\n";
|
|
24
|
+
|
|
25
|
+
if (!existsSync(path)) {
|
|
26
|
+
return `${header}\n_Database not found at \`${path}\`. Run \`ppm init\` to create it._\n`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let db: Database | null = null;
|
|
30
|
+
try {
|
|
31
|
+
db = new Database(path, { readonly: true });
|
|
32
|
+
const tables = db
|
|
33
|
+
.query<TableRow, []>("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name")
|
|
34
|
+
.all();
|
|
35
|
+
|
|
36
|
+
if (tables.length === 0) {
|
|
37
|
+
return `${header}\n_No tables found in \`${path}\`._\n`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const parts: string[] = [header, ""];
|
|
41
|
+
parts.push(`Source: \`${path}\``);
|
|
42
|
+
parts.push("");
|
|
43
|
+
|
|
44
|
+
for (const t of tables) {
|
|
45
|
+
parts.push(`## ${t.name}`);
|
|
46
|
+
parts.push("");
|
|
47
|
+
const cols = db.query<ColumnInfo, []>(`PRAGMA table_info("${t.name}")`).all();
|
|
48
|
+
parts.push("| Column | Type | Nullable | PK | Default |");
|
|
49
|
+
parts.push("|---|---|---|---|---|");
|
|
50
|
+
for (const c of cols) {
|
|
51
|
+
const nullable = c.notnull === 0 ? "yes" : "no";
|
|
52
|
+
const pk = c.pk > 0 ? "yes" : "";
|
|
53
|
+
const def = c.dflt_value !== null ? `\`${c.dflt_value}\`` : "";
|
|
54
|
+
parts.push(`| \`${c.name}\` | \`${c.type || "—"}\` | ${nullable} | ${pk} | ${def} |`);
|
|
55
|
+
}
|
|
56
|
+
parts.push("");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return parts.join("\n") + "\n";
|
|
60
|
+
} catch (e) {
|
|
61
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
62
|
+
return `${header}\n_Failed to read database at \`${path}\`: ${msg}_\n`;
|
|
63
|
+
} finally {
|
|
64
|
+
db?.close();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Barrel re-exports for the skill-export service.
|
|
2
|
+
export { resolveTargetDir, type SkillScope, type ResolveTargetOpts } from "./resolve-target-dir.ts";
|
|
3
|
+
export { resolveAssetsDir } from "./resolve-assets-dir.ts";
|
|
4
|
+
export { backupExisting, makeTimestamp } from "./backup-existing.ts";
|
|
5
|
+
export { copyBundledSkill } from "./copy-bundled-skill.ts";
|
|
6
|
+
export { generateDbSchemaMarkdown } from "./generate-db-schema.ts";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Resolve the bundled skill assets dir. Works both in dev (`bun src/index.ts`)
|
|
2
|
+
// and installed npm package. Throws a clear error if assets are missing.
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { resolve, dirname } from "node:path";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
const ASSETS_REL = "assets/skills/ppm";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Walks up from this file to find the repo/package root that contains
|
|
11
|
+
* `assets/skills/ppm/SKILL.md`. Covers both:
|
|
12
|
+
* - dev: src/services/skill-export/*.ts → repo root is ../../..
|
|
13
|
+
* - bundled: compiled binary walks up similarly when `bun build --compile` preserves structure
|
|
14
|
+
*/
|
|
15
|
+
export function resolveAssetsDir(): string {
|
|
16
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const candidates = [
|
|
18
|
+
resolve(here, "../../..", ASSETS_REL),
|
|
19
|
+
resolve(here, "../..", ASSETS_REL),
|
|
20
|
+
resolve(here, "..", ASSETS_REL),
|
|
21
|
+
resolve(process.cwd(), ASSETS_REL),
|
|
22
|
+
];
|
|
23
|
+
for (const c of candidates) {
|
|
24
|
+
if (existsSync(resolve(c, "SKILL.md"))) return c;
|
|
25
|
+
}
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Bundled PPM skill assets not found. Searched:\n ${candidates.join(
|
|
28
|
+
"\n ",
|
|
29
|
+
)}\nRun \`bun run generate:skill\` (dev) or reinstall PPM.`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Resolve the install target directory based on scope/output flags.
|
|
2
|
+
// Precedence: --output > --scope project > --scope user (default).
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
|
|
6
|
+
export type SkillScope = "user" | "project";
|
|
7
|
+
|
|
8
|
+
export interface ResolveTargetOpts {
|
|
9
|
+
scope?: SkillScope;
|
|
10
|
+
output?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveTargetDir(opts: ResolveTargetOpts): string {
|
|
14
|
+
if (opts.output) return resolve(opts.output);
|
|
15
|
+
if (opts.scope === "project") return resolve(process.cwd(), ".claude/skills/ppm");
|
|
16
|
+
return resolve(homedir(), ".claude/skills/ppm");
|
|
17
|
+
}
|