dotmd-cli 0.75.0 → 0.76.0
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/bin/dotmd.mjs +12 -0
- package/package.json +1 -1
- package/src/host-integration.mjs +54 -0
package/bin/dotmd.mjs
CHANGED
|
@@ -1594,6 +1594,18 @@ async function main() {
|
|
|
1594
1594
|
throw err;
|
|
1595
1595
|
}
|
|
1596
1596
|
_resolvedConfig = config;
|
|
1597
|
+
// The one place that reaches an OpenCode session running without the
|
|
1598
|
+
// integration — whatever verb it reaches for first. stderr, so `--json`
|
|
1599
|
+
// consumers are untouched, and once per session so it informs rather than
|
|
1600
|
+
// nags. `install`/`doctor`/`update` are skipped: they report this themselves,
|
|
1601
|
+
// and telling `dotmd install opencode` to run `dotmd install opencode` is
|
|
1602
|
+
// noise.
|
|
1603
|
+
if (!['install', 'doctor', 'update', 'hud', 'guard'].includes(command)) {
|
|
1604
|
+
const { degradedIdentityNotice } = await import('../src/host-integration.mjs');
|
|
1605
|
+
const notice = degradedIdentityNotice(config?.repoRoot, { version: pkg.version });
|
|
1606
|
+
if (notice) process.stderr.write(`${notice}\n`);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1597
1609
|
const suppressSideEffects = effectiveDryRun || command === 'hud' || passiveMachineContext;
|
|
1598
1610
|
Object.defineProperty(config, '_execution', {
|
|
1599
1611
|
value: { dryRun, passive: command === 'hud' || passiveMachineContext, suppressSideEffects, gitStaleness },
|
package/package.json
CHANGED
package/src/host-integration.mjs
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// reports that the integration is missing and names the command; it never
|
|
17
17
|
// installs. Passive commands touch nothing.
|
|
18
18
|
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
19
20
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
20
21
|
import os from 'node:os';
|
|
21
22
|
import path from 'node:path';
|
|
@@ -166,6 +167,59 @@ export function describeSessionIdentity(opts = {}) {
|
|
|
166
167
|
};
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
// --- Telling the session it is running degraded -----------------------------
|
|
171
|
+
//
|
|
172
|
+
// Every other surface here has to be sought out: `doctor`, `install`, the npm
|
|
173
|
+
// postinstall line. But the degraded mode is SILENT — with the OPENCODE_PID
|
|
174
|
+
// fallback in place, `use`/`set`/`baton` all work, they just share one identity
|
|
175
|
+
// across every session in the OpenCode process. Nobody goes looking for a
|
|
176
|
+
// diagnostic about a thing that appears to work, so this comes to them.
|
|
177
|
+
//
|
|
178
|
+
// Warning, not error. Failing closed would undo the fallback that unblocked
|
|
179
|
+
// OpenCode in the first place, and punish a setup that is working.
|
|
180
|
+
//
|
|
181
|
+
// Once per session per repo, via a marker under the gitignored .dotmd/. Silent
|
|
182
|
+
// under DOTMD_NO_HINTS=1, the switch the repeat-failure hints already use.
|
|
183
|
+
const NOTICE_DIR = 'notices';
|
|
184
|
+
|
|
185
|
+
function noticeMarkerPath(repoRoot, key) {
|
|
186
|
+
const digest = createHash('sha256').update(key).digest('hex').slice(0, 32);
|
|
187
|
+
return path.join(path.resolve(repoRoot), '.dotmd', NOTICE_DIR, `${digest}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Returns the notice text the first time it applies in a session, then null.
|
|
191
|
+
// `record: false` answers without consuming the once-per-session budget.
|
|
192
|
+
export function degradedIdentityNotice(repoRoot, opts = {}) {
|
|
193
|
+
const { env = process.env, homedir = os.homedir(), version, record = true } = opts;
|
|
194
|
+
if (env.DOTMD_NO_HINTS === '1') return null;
|
|
195
|
+
// Cheap env check first: a user on any other host pays nothing for this.
|
|
196
|
+
if (!env.OPENCODE && !env.OPENCODE_PID) return null;
|
|
197
|
+
|
|
198
|
+
const identity = describeSessionIdentity({ env, homedir, version });
|
|
199
|
+
if (identity.scope === 'session') return null;
|
|
200
|
+
|
|
201
|
+
const installed = opencodeStatus({ env, homedir, version }).exists;
|
|
202
|
+
const lines = installed
|
|
203
|
+
? ['[dotmd] This OpenCode session started before the dotmd integration loaded, so its plan',
|
|
204
|
+
' ownership is still process-scoped. Restart OpenCode to pick it up.']
|
|
205
|
+
: ['[dotmd] OpenCode detected, dotmd integration not installed — plan ownership is scoped to',
|
|
206
|
+
' the OpenCode process, so another session in it can release plans this one claimed,',
|
|
207
|
+
' and no session-start briefing runs. Fix once: dotmd install opencode'];
|
|
208
|
+
|
|
209
|
+
if (!record) return lines.join('\n');
|
|
210
|
+
if (!repoRoot) return lines.join('\n');
|
|
211
|
+
const marker = noticeMarkerPath(repoRoot, `opencode:${identity.id}:${installed ? 'restart' : 'install'}`);
|
|
212
|
+
if (existsSync(marker)) return null;
|
|
213
|
+
try {
|
|
214
|
+
mkdirSync(path.dirname(marker), { recursive: true });
|
|
215
|
+
writeFileSync(marker, '', 'utf8');
|
|
216
|
+
} catch {
|
|
217
|
+
// Unwritable .dotmd: say it once anyway rather than stay silent about a
|
|
218
|
+
// real ownership weakness. Worst case it repeats.
|
|
219
|
+
}
|
|
220
|
+
return lines.join('\n');
|
|
221
|
+
}
|
|
222
|
+
|
|
169
223
|
// --- Claude Code -----------------------------------------------------------
|
|
170
224
|
//
|
|
171
225
|
// Claude Code has a real plugin registry, so dotmd drives `claude plugin`
|