-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
144 lines (131 loc) · 5.57 KB
/
Copy pathindex.js
File metadata and controls
144 lines (131 loc) · 5.57 KB
1
2
3
4
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// dsh-programming-mode — profile-boot installer for the 编程模式 agent preset.
//
// A DSH bundle cannot register an agent-preset root: the launcher composes the
// `agent-presets` row last and pins its `roots` config to the shipped root.
// Distribution therefore works by planting the bundled preset directory into
// the roster's first user-trust preset root. Discovery is uncached (every
// list() re-reads the roots), so the preset appears in the mode picker right
// after this plugin has planted it.
//
// The planted preset is SELF-CONTAINED: its one bundle-owned row
// (`force-superpowers`) references a module inside the preset directory
// (preset/programming/force-superpowers.mjs), never this package. The module
// also owns the uninstall story: pnpm runs no lifecycle hooks of removed
// packages and the market dispatches no uninstall events, so the planted copy
// self-removes at the next host boot once no profile installs this package
// anymore (stamp-guarded). A row naming the package would instead leave every
// session recorded on the preset unresumable (ERR_MODULE_NOT_FOUND at mount).
//
// Planting policy (deliberate, documented in README.md):
// - target absent -> plant fresh, write version stamp
// - stamp matches our version-> no-op (preserve local edits)
// - stamp differs (upgrade) -> overwrite files, refresh stamp
// - no stamp (foreign dir) -> refuse to touch, warn once per boot
//
// Uninstalling the bundle deletes the planted preset at the next boot (the
// preset's own mounted module checks every profile's package.json), so
// "uninstall, restart" is the whole story. `preset/programming/uninstall.mjs`
// is the immediate, no-restart variant.
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const PACKAGE_DIR = dirname(fileURLToPath(import.meta.url))
export const name = 'programming-mode-installer'
const PRESET_ID = 'programming'
const STAMP_FILENAME = '.dsh-programming-mode.installed.json'
function readOwnVersion() {
try {
const manifest = JSON.parse(readFileSync(join(PACKAGE_DIR, 'package.json'), 'utf8'))
return String(manifest.version ?? '0.0.0')
} catch {
return '0.0.0'
}
}
// The roster knows its own roots better than any guess: prefer the first
// user-trust root it reports (that is where authoring writes land), fall back
// to the derived default ($DSH_HOME or ~/.dsh + .agent-presets).
function resolveTargetParent(agentPresets) {
try {
const roots = agentPresets?.roots
if (Array.isArray(roots)) {
const hit = roots.find((root) => root && root.trust === 'user' && typeof root.path === 'string')
if (hit) return dirname(hit.path)
}
} catch {
// fall through to the derived default below
}
const envHome = typeof process.env.DSH_HOME === 'string' && process.env.DSH_HOME.trim() !== ''
? process.env.DSH_HOME.trim()
: join(homedir(), '.dsh')
return join(envHome, '.agent-presets')
}
function readStamp(targetDir) {
try {
const stamp = JSON.parse(readFileSync(join(targetDir, STAMP_FILENAME), 'utf8'))
return typeof stamp?.version === 'string' ? stamp : null
} catch {
return null
}
}
function writeStamp(targetDir, version) {
writeFileSync(
join(targetDir, STAMP_FILENAME),
JSON.stringify({ version, source: 'dsh-programming-mode', plantedAt: new Date().toISOString() }, null, 2) + '\n',
)
}
/**
* Plant (or update) the bundled 编程模式 preset into a preset root.
* Exported separately from apply() so tests can drive it with explicit paths.
* @param {{
* sourceDir?: string,
* targetParent?: string,
* presetId?: string,
* version?: string,
* agentPresets?: { roots?: Array<{ path?: string, trust?: string }> },
* }} [options]
*/
export function plantPreset(options = {}) {
const sourceDir = resolve(options.sourceDir ?? join(PACKAGE_DIR, 'preset', PRESET_ID))
const presetId = String(options.presetId ?? PRESET_ID)
const targetParent = resolve(options.targetParent ?? resolveTargetParent(options.agentPresets))
const targetDir = join(targetParent, presetId)
const version = String(options.version ?? readOwnVersion())
if (!existsSync(sourceDir)) {
return { action: 'error', reason: `bundled preset missing: ${sourceDir}`, targetDir }
}
if (!existsSync(targetDir)) {
mkdirSync(targetParent, { recursive: true })
cpSync(sourceDir, targetDir, { recursive: true })
writeStamp(targetDir, version)
return { action: 'planted', version, targetDir }
}
const stamped = readStamp(targetDir)
if (stamped === null) {
return {
action: 'skipped',
reason: `${targetDir} exists without an installer stamp; refusing to touch a preset we did not plant`,
targetDir,
}
}
// A tombstone (uninstall leftover, see preset/programming/force-superpowers.mjs)
// is repaired even at the same version: the user reinstalled the bundle, so
// the full mode must come back.
if (stamped.tombstone === true || stamped.version !== version) {
cpSync(sourceDir, targetDir, { recursive: true, force: true })
writeStamp(targetDir, version)
return { action: 'updated', from: stamped.version, to: version, targetDir }
}
return { action: 'unchanged', version, targetDir }
}
export function apply(ctx) {
let agentPresets
try {
agentPresets = ctx.get('agentPresets')
} catch {
agentPresets = undefined
}
const result = plantPreset({ agentPresets })
const detail = result.reason ?? (result.action === 'updated' ? `${result.from} -> ${result.to}` : result.version ?? '')
console.log(`[dsh-programming-mode] ${result.action}${detail ? `: ${detail}` : ''}`)
}