devworkspace 0.1.0 → 0.2.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +16 -2
  3. package/dist/agents/claude/adapter.d.ts +5 -0
  4. package/dist/agents/claude/adapter.js +10 -0
  5. package/dist/agents/claude/adapter.js.map +1 -1
  6. package/dist/agents/claude/content.js +3 -3
  7. package/dist/agents/claude/content.js.map +1 -1
  8. package/dist/cli/args.d.ts +22 -0
  9. package/dist/cli/args.js +7 -0
  10. package/dist/cli/args.js.map +1 -1
  11. package/dist/cli/help.js +54 -7
  12. package/dist/cli/help.js.map +1 -1
  13. package/dist/cli/render.d.ts +9 -0
  14. package/dist/cli/render.js +105 -13
  15. package/dist/cli/render.js.map +1 -1
  16. package/dist/cli/run.d.ts +6 -0
  17. package/dist/cli/run.js +75 -3
  18. package/dist/cli/run.js.map +1 -1
  19. package/dist/core/workspace.js +2 -1
  20. package/dist/core/workspace.js.map +1 -1
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.js +2 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/integrations/adapters/agent-skills.d.ts +8 -0
  25. package/dist/integrations/adapters/agent-skills.js +25 -0
  26. package/dist/integrations/adapters/agent-skills.js.map +1 -0
  27. package/dist/integrations/adapters/claudePlugin.d.ts +41 -0
  28. package/dist/integrations/adapters/claudePlugin.js +129 -0
  29. package/dist/integrations/adapters/claudePlugin.js.map +1 -0
  30. package/dist/integrations/adapters/graphify.d.ts +2 -0
  31. package/dist/integrations/adapters/graphify.js +239 -0
  32. package/dist/integrations/adapters/graphify.js.map +1 -0
  33. package/dist/integrations/adapters/omniroute.d.ts +6 -0
  34. package/dist/integrations/adapters/omniroute.js +32 -0
  35. package/dist/integrations/adapters/omniroute.js.map +1 -0
  36. package/dist/integrations/adapters/pluginSkill.d.ts +28 -0
  37. package/dist/integrations/adapters/pluginSkill.js +195 -0
  38. package/dist/integrations/adapters/pluginSkill.js.map +1 -0
  39. package/dist/integrations/adapters/ponytail.d.ts +8 -0
  40. package/dist/integrations/adapters/ponytail.js +33 -0
  41. package/dist/integrations/adapters/ponytail.js.map +1 -0
  42. package/dist/integrations/exec.d.ts +26 -0
  43. package/dist/integrations/exec.js +167 -0
  44. package/dist/integrations/exec.js.map +1 -0
  45. package/dist/integrations/index.d.ts +5 -6
  46. package/dist/integrations/index.js +3 -29
  47. package/dist/integrations/index.js.map +1 -1
  48. package/dist/integrations/manager.d.ts +62 -0
  49. package/dist/integrations/manager.js +248 -0
  50. package/dist/integrations/manager.js.map +1 -0
  51. package/dist/integrations/notices.d.ts +13 -0
  52. package/dist/integrations/notices.js +68 -0
  53. package/dist/integrations/notices.js.map +1 -0
  54. package/dist/integrations/protect.d.ts +20 -0
  55. package/dist/integrations/protect.js +131 -0
  56. package/dist/integrations/protect.js.map +1 -0
  57. package/dist/integrations/state.d.ts +10 -0
  58. package/dist/integrations/state.js +81 -0
  59. package/dist/integrations/state.js.map +1 -0
  60. package/dist/integrations/types.d.ts +133 -16
  61. package/dist/integrations/types.js +13 -1
  62. package/dist/integrations/types.js.map +1 -1
  63. package/dist/prompts/onboarding.d.ts +3 -3
  64. package/dist/prompts/onboarding.js +5 -4
  65. package/dist/prompts/onboarding.js.map +1 -1
  66. package/dist/services/analysis.d.ts +2 -2
  67. package/dist/services/analysis.js +4 -5
  68. package/dist/services/analysis.js.map +1 -1
  69. package/dist/services/common.d.ts +8 -5
  70. package/dist/services/common.js +2 -5
  71. package/dist/services/common.js.map +1 -1
  72. package/dist/services/doctor.d.ts +10 -0
  73. package/dist/services/doctor.js +9 -15
  74. package/dist/services/doctor.js.map +1 -1
  75. package/dist/services/init.d.ts +9 -2
  76. package/dist/services/init.js +10 -7
  77. package/dist/services/init.js.map +1 -1
  78. package/dist/services/integrations.d.ts +26 -0
  79. package/dist/services/integrations.js +35 -0
  80. package/dist/services/integrations.js.map +1 -0
  81. package/dist/services/reset.d.ts +23 -0
  82. package/dist/services/reset.js +96 -0
  83. package/dist/services/reset.js.map +1 -0
  84. package/dist/services/status.d.ts +3 -2
  85. package/dist/services/status.js +3 -3
  86. package/dist/services/status.js.map +1 -1
  87. package/dist/utils/lock.d.ts +2 -0
  88. package/dist/utils/lock.js +2 -2
  89. package/dist/utils/lock.js.map +1 -1
  90. package/dist/utils/proc.d.ts +8 -1
  91. package/dist/utils/proc.js +31 -8
  92. package/dist/utils/proc.js.map +1 -1
  93. package/docs/architecture.md +1 -1
  94. package/docs/claude.md +4 -0
  95. package/docs/getting-started.md +13 -2
  96. package/docs/integrations.md +98 -33
  97. package/docs/security.md +5 -1
  98. package/docs/testing.md +2 -0
  99. package/docs/troubleshooting.md +9 -1
  100. package/package.json +1 -1
  101. package/dist/integrations/agent-skills.d.ts +0 -10
  102. package/dist/integrations/agent-skills.js +0 -45
  103. package/dist/integrations/agent-skills.js.map +0 -1
  104. package/dist/integrations/graphify.d.ts +0 -7
  105. package/dist/integrations/graphify.js +0 -54
  106. package/dist/integrations/graphify.js.map +0 -1
  107. package/dist/integrations/omniroute.d.ts +0 -7
  108. package/dist/integrations/omniroute.js +0 -24
  109. package/dist/integrations/omniroute.js.map +0 -1
  110. package/dist/integrations/ponytail.d.ts +0 -10
  111. package/dist/integrations/ponytail.js +0 -58
  112. package/dist/integrations/ponytail.js.map +0 -1
  113. package/dist/integrations/probe.d.ts +0 -6
  114. package/dist/integrations/probe.js +0 -41
  115. package/dist/integrations/probe.js.map +0 -1
@@ -0,0 +1,248 @@
1
+ import os from 'node:os';
2
+ import { onCleanup } from '../utils/cleanup.js';
3
+ import { withLock } from '../utils/lock.js';
4
+ import { redact } from '../security/redact.js';
5
+ import { findAbsolutePaths } from '../utils/paths.js';
6
+ import { agentSkills } from './adapters/agent-skills.js';
7
+ import { graphify } from './adapters/graphify.js';
8
+ import { omniroute } from './adapters/omniroute.js';
9
+ import { ponytail } from './adapters/ponytail.js';
10
+ import { createRunner } from './exec.js';
11
+ import { restoreSnapshot, takeSnapshot, verifySnapshot } from './protect.js';
12
+ import { blankEntry, loadIntegrationsState, saveIntegrationEntry, wasInterrupted } from './state.js';
13
+ import { REQUIRED_INTEGRATIONS, } from './types.js';
14
+ /** Tool output goes into reports and the state file: strip secrets and machine-specific paths, flatten to one line. */
15
+ export function scrub(text) {
16
+ let out = redact(text);
17
+ for (const p of findAbsolutePaths(out))
18
+ out = out.split(p).join('<path>');
19
+ return out.replace(/\s+/g, ' ').trim().slice(0, 400);
20
+ }
21
+ export const ADAPTERS = [graphify, agentSkills, ponytail, omniroute];
22
+ function resolveEnv(m) {
23
+ const env = m.env ?? process.env;
24
+ const platform = m.platform ?? process.platform;
25
+ return {
26
+ root: m.root,
27
+ home: m.home ?? os.homedir(),
28
+ env,
29
+ platform,
30
+ runner: m.runner ?? createRunner(env, platform),
31
+ now: m.now ?? (() => new Date()),
32
+ adapters: m.adapters ?? ADAPTERS,
33
+ };
34
+ }
35
+ function ctxFor(m, setup, prior, report, signal) {
36
+ return { root: m.root, home: m.home, env: m.env, platform: m.platform, runner: m.runner, setup, prior, report, signal };
37
+ }
38
+ const DEFAULT_SETUP = { scope: 'project', yes: false, dryRun: false, repair: false };
39
+ /** Combine what was observed with what earlier runs recorded. Records can only add warnings or RESTART_REQUIRED, never VERIFIED. */
40
+ export function applyPersisted(report, entry) {
41
+ if (!entry)
42
+ return report;
43
+ const out = { ...report, warnings: [...report.warnings], nextSteps: [...report.nextSteps] };
44
+ if (wasInterrupted(entry)) {
45
+ out.warnings.push('A previous setup run was interrupted before it finished.');
46
+ if (!out.nextSteps.includes('devworkspace integrations repair --yes'))
47
+ out.nextSteps.push('devworkspace integrations repair --yes');
48
+ }
49
+ if ((out.status === 'NOT_INSTALLED' || out.status === 'DEGRADED') && entry.lastError) {
50
+ out.status = 'FAILED';
51
+ out.warnings.push(`The last setup attempt failed: ${entry.lastError}`);
52
+ }
53
+ if (entry.restartRequiredAt && (out.status === 'VERIFIED' || out.status === 'INSTALLED')) {
54
+ out.status = 'RESTART_REQUIRED';
55
+ out.nextSteps.push('Restart Claude Code (or run /reload-plugins) so it loads the new skills/plugins/hooks, then run: devworkspace integrations status --restarted');
56
+ }
57
+ return out;
58
+ }
59
+ export async function inspectAll(ws, mEnv, opts = { deep: false }) {
60
+ const m = resolveEnv(mEnv);
61
+ const state = await loadIntegrationsState(ws, m.now().toISOString());
62
+ const list = m.adapters.filter((a) => !opts.onlyRequired || a.required);
63
+ return Promise.all(list.map(async (a) => {
64
+ const ctx = ctxFor(m, DEFAULT_SETUP, state.integrations[a.id], () => undefined);
65
+ try {
66
+ return applyPersisted(await a.inspect(ctx, opts.deep), state.integrations[a.id]);
67
+ }
68
+ catch (e) {
69
+ return {
70
+ id: a.id,
71
+ name: a.name,
72
+ required: a.required,
73
+ status: 'FAILED',
74
+ variant: null,
75
+ scope: null,
76
+ version: null,
77
+ checks: [],
78
+ capabilities: {},
79
+ warnings: [`Inspection failed: ${scrub(e.message)}`],
80
+ nextSteps: ['devworkspace doctor --debug'],
81
+ deep: opts.deep,
82
+ };
83
+ }
84
+ }));
85
+ }
86
+ /** Summary used by doctor/status: required integrations only; OmniRoute never counts. */
87
+ export function summarize(reports) {
88
+ const required = reports.filter((r) => r.required);
89
+ const pending = required.filter((r) => r.status !== 'VERIFIED');
90
+ return { complete: pending.length === 0, required: required.length, verified: required.length - pending.length, pending };
91
+ }
92
+ export async function acknowledgeRestart(ws, ids, now) {
93
+ for (const id of ids)
94
+ await saveIntegrationEntry(ws, id, { restartRequiredAt: null }, now);
95
+ }
96
+ const describeStep = (s) => ({ description: s.description, command: s.command, level: s.level });
97
+ export async function runSetup(ws, mEnv, setup, onEvent = () => undefined) {
98
+ const m = resolveEnv(mEnv);
99
+ const events = [];
100
+ const emit = (e) => {
101
+ events.push(e);
102
+ onEvent(e);
103
+ };
104
+ const ac = new AbortController();
105
+ const disposeCancel = onCleanup(() => ac.abort());
106
+ const wanted = m.adapters.filter((a) => (setup.only ? setup.only.includes(a.id) : REQUIRED_INTEGRATIONS.includes(a.id)));
107
+ const outcomes = [];
108
+ let approvalRequired = false;
109
+ try {
110
+ for (const adapter of wanted) {
111
+ const nowIso = () => m.now().toISOString();
112
+ const state = await loadIntegrationsState(ws, nowIso());
113
+ const prior = state.integrations[adapter.id];
114
+ const ctx = ctxFor(m, setup, prior, emit, ac.signal);
115
+ emit({ id: adapter.id, step: 'inspect', state: 'start', detail: `checking ${adapter.name}` });
116
+ const before = applyPersisted(await adapter.inspect(ctx, true), prior);
117
+ const outcome = (action, report, extra = {}) => ({
118
+ id: adapter.id,
119
+ name: adapter.name,
120
+ action,
121
+ before: before.status,
122
+ report,
123
+ plan: [],
124
+ steps: [],
125
+ notes: [],
126
+ ...extra,
127
+ });
128
+ if (before.status === 'VERIFIED' && !setup.repair) {
129
+ emit({ id: adapter.id, step: 'inspect', state: 'skip', detail: 'already verified' });
130
+ outcomes.push(outcome('already-verified', before));
131
+ continue;
132
+ }
133
+ if (before.status === 'RESTART_REQUIRED' && !setup.repair) {
134
+ emit({ id: adapter.id, step: 'inspect', state: 'skip', detail: 'installed; waiting for a Claude Code restart' });
135
+ outcomes.push(outcome('awaiting-restart', before));
136
+ continue;
137
+ }
138
+ const plan = await adapter.plan(ctx, before);
139
+ if (plan.steps.length === 0) {
140
+ if (plan.blockers.length > 0) {
141
+ emit({ id: adapter.id, step: 'prerequisites', state: 'fail', detail: plan.blockers[0] });
142
+ outcomes.push(outcome('blocked', { ...before, status: 'PREREQUISITES_MISSING', nextSteps: plan.blockers }, { notes: plan.notes }));
143
+ }
144
+ else {
145
+ outcomes.push(outcome('nothing-to-do', before, { notes: plan.notes }));
146
+ }
147
+ continue;
148
+ }
149
+ const planInfo = plan.steps.map(describeStep);
150
+ if (setup.dryRun) {
151
+ outcomes.push(outcome('dry-run', before, { plan: planInfo, notes: plan.notes }));
152
+ continue;
153
+ }
154
+ const needsConsent = plan.steps.some((s) => s.level !== 'project');
155
+ if (needsConsent && !setup.yes) {
156
+ approvalRequired = true;
157
+ const levels = [...new Set(plan.steps.filter((s) => s.level !== 'project').map((s) => s.level))];
158
+ outcomes.push(outcome('approval-required', { ...before, nextSteps: [`Re-run with --yes to approve: ${levels.join(', ')} step(s). Review them first with --dry-run.`, ...before.nextSteps] }, { plan: planInfo, notes: plan.notes }));
159
+ continue;
160
+ }
161
+ // ---- execute ----
162
+ const results = plan.steps.map((s) => ({ id: s.id, description: s.description, command: s.command, level: s.level, state: 'not-run' }));
163
+ const managedPrior = prior?.managedPaths ?? [];
164
+ const isManaged = (rel) => managedPrior.some((p) => rel === p || rel.startsWith(`${p}/`));
165
+ const managedNew = new Set(managedPrior);
166
+ let restartRequired = false;
167
+ let failure = null;
168
+ const hints = [];
169
+ await withLock(ws.dir, async () => {
170
+ await saveIntegrationEntry(ws, adapter.id, { installing: { pid: process.pid, startedAt: nowIso(), step: plan.steps[0].id }, lastAttemptAt: nowIso() }, nowIso());
171
+ const snap = await takeSnapshot(m.root, plan.protectPaths, ['.claude/skills'], isManaged);
172
+ for (let i = 0; i < plan.steps.length && !failure; i++) {
173
+ const step = plan.steps[i];
174
+ if (ac.signal.aborted) {
175
+ failure = 'cancelled';
176
+ break;
177
+ }
178
+ emit({ id: adapter.id, step: step.id, state: 'start', detail: step.description });
179
+ let out;
180
+ try {
181
+ out = await step.run();
182
+ }
183
+ catch (e) {
184
+ out = { ok: false, detail: e.message };
185
+ }
186
+ const violations = await verifySnapshot(snap);
187
+ if (violations.length > 0) {
188
+ await restoreSnapshot(snap, violations);
189
+ out = { ok: false, detail: `Refused: this step would have overwritten your files (${violations.map((v) => `${v.path}: ${v.reason}`).join('; ')}). They were restored.` };
190
+ }
191
+ const res = results[i];
192
+ res.state = out.ok ? 'ok' : 'failed';
193
+ res.detail = out.detail ? scrub(out.detail) : undefined;
194
+ if (!out.ok && out.hint)
195
+ hints.push(out.hint);
196
+ res.requiresApproval = out.requiresApproval;
197
+ emit({ id: adapter.id, step: step.id, state: out.ok ? 'ok' : 'fail', detail: res.detail ?? step.description });
198
+ if (out.ok) {
199
+ if (out.restartRequired)
200
+ restartRequired = true;
201
+ for (const p of out.managedPaths ?? [])
202
+ managedNew.add(p);
203
+ }
204
+ else
205
+ failure = res.detail ?? 'step failed';
206
+ }
207
+ }, { name: 'integrations.lock', staleMs: 30 * 60_000, timeoutMs: 10_000 });
208
+ const after = await adapterInspectAfter(ws, adapter, m, setup, emit, nowIso());
209
+ await saveIntegrationEntry(ws, adapter.id, {
210
+ installing: null,
211
+ lastError: failure,
212
+ managedPaths: [...managedNew].sort(),
213
+ ...(restartRequired && !failure ? { restartRequiredAt: nowIso() } : {}),
214
+ }, nowIso());
215
+ const finalState = (await loadIntegrationsState(ws, nowIso())).integrations[adapter.id] ?? blankEntry();
216
+ let finalReport = applyPersisted(after, finalState);
217
+ if (failure && finalReport.status !== 'VERIFIED')
218
+ finalReport = { ...finalReport, status: 'FAILED', warnings: [...finalReport.warnings, `Setup stopped: ${failure}`] };
219
+ if (hints.length > 0)
220
+ finalReport = { ...finalReport, nextSteps: [...hints, ...finalReport.nextSteps] };
221
+ const approvalStep = results.find((r) => r.requiresApproval);
222
+ if (approvalStep?.command)
223
+ finalReport = { ...finalReport, nextSteps: [`Run this yourself in a terminal (Claude Code needs your approval): ${approvalStep.command}`, ...finalReport.nextSteps] };
224
+ await saveIntegrationEntry(ws, adapter.id, { status: finalReport.status, variant: finalReport.variant, scope: finalReport.scope, version: finalReport.version, lastVerifiedAt: finalReport.status === 'VERIFIED' ? nowIso() : null }, nowIso());
225
+ outcomes.push(outcome(failure ? 'failed' : 'installed', finalReport, { plan: planInfo, steps: results, notes: plan.notes }));
226
+ }
227
+ }
228
+ finally {
229
+ disposeCancel();
230
+ }
231
+ const required = outcomes.filter((o) => o.report.required);
232
+ return {
233
+ dryRun: setup.dryRun,
234
+ scope: setup.scope,
235
+ outcomes,
236
+ events,
237
+ complete: required.length > 0 && required.every((o) => o.report.status === 'VERIFIED'),
238
+ approvalRequired,
239
+ };
240
+ }
241
+ async function adapterInspectAfter(ws, adapter, m, setup, emit, now) {
242
+ const state = await loadIntegrationsState(ws, now);
243
+ emit({ id: adapter.id, step: 'verify', state: 'start', detail: 'verifying the installation' });
244
+ const report = await adapter.inspect(ctxFor(m, setup, state.integrations[adapter.id], emit), true);
245
+ emit({ id: adapter.id, step: 'verify', state: report.status === 'VERIFIED' ? 'ok' : 'info', detail: report.status });
246
+ return report;
247
+ }
248
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/integrations/manager.ts"],"names":[],"mappings":"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","sourcesContent":["import os from 'node:os';\nimport type { Ws } from '../core/workspace.js';\nimport { onCleanup } from '../utils/cleanup.js';\nimport { withLock } from '../utils/lock.js';\nimport { redact } from '../security/redact.js';\nimport { findAbsolutePaths } from '../utils/paths.js';\nimport { agentSkills } from './adapters/agent-skills.js';\nimport { graphify } from './adapters/graphify.js';\nimport { omniroute } from './adapters/omniroute.js';\nimport { ponytail } from './adapters/ponytail.js';\nimport { createRunner } from './exec.js';\nimport { restoreSnapshot, takeSnapshot, verifySnapshot } from './protect.js';\nimport { blankEntry, loadIntegrationsState, saveIntegrationEntry, wasInterrupted } from './state.js';\nimport {\n REQUIRED_INTEGRATIONS,\n type AdapterContext,\n type IntegrationAdapter,\n type IntegrationId,\n type IntegrationReport,\n type PersistedIntegration,\n type PlanStep,\n type ProgressEvent,\n type Runner,\n type SetupOptions,\n type StatusCode,\n type StepLevel,\n} from './types.js';\n\n/** Tool output goes into reports and the state file: strip secrets and machine-specific paths, flatten to one line. */\nexport function scrub(text: string): string {\n let out = redact(text);\n for (const p of findAbsolutePaths(out)) out = out.split(p).join('<path>');\n return out.replace(/\\s+/g, ' ').trim().slice(0, 400);\n}\n\nexport const ADAPTERS: IntegrationAdapter[] = [graphify, agentSkills, ponytail, omniroute];\n\nexport interface ManagerEnv {\n root: string;\n home?: string;\n env?: NodeJS.ProcessEnv;\n platform?: NodeJS.Platform;\n runner?: Runner;\n now?: () => Date;\n adapters?: IntegrationAdapter[];\n}\n\nexport interface StepResult {\n id: string;\n description: string;\n command?: string;\n level: StepLevel;\n state: 'ok' | 'failed' | 'not-run';\n detail?: string;\n requiresApproval?: boolean;\n}\n\nexport type OutcomeAction =\n | 'already-verified'\n | 'awaiting-restart'\n | 'nothing-to-do'\n | 'blocked'\n | 'dry-run'\n | 'approval-required'\n | 'installed'\n | 'failed'\n | 'not-selected';\n\nexport interface IntegrationOutcome {\n id: IntegrationId;\n name: string;\n action: OutcomeAction;\n before: StatusCode;\n report: IntegrationReport;\n plan: { description: string; command?: string; level: StepLevel }[];\n steps: StepResult[];\n notes: string[];\n}\n\nexport interface SetupResult {\n dryRun: boolean;\n scope: SetupOptions['scope'];\n outcomes: IntegrationOutcome[];\n events: ProgressEvent[];\n /** Every requested required integration is VERIFIED. */\n complete: boolean;\n approvalRequired: boolean;\n}\n\nfunction resolveEnv(m: ManagerEnv): Required<Pick<ManagerEnv, 'root' | 'home' | 'env' | 'platform' | 'runner' | 'now' | 'adapters'>> {\n const env = m.env ?? process.env;\n const platform = m.platform ?? process.platform;\n return {\n root: m.root,\n home: m.home ?? os.homedir(),\n env,\n platform,\n runner: m.runner ?? createRunner(env, platform),\n now: m.now ?? (() => new Date()),\n adapters: m.adapters ?? ADAPTERS,\n };\n}\n\nfunction ctxFor(m: ReturnType<typeof resolveEnv>, setup: SetupOptions, prior: PersistedIntegration | undefined, report: (e: ProgressEvent) => void, signal?: AbortSignal): AdapterContext {\n return { root: m.root, home: m.home, env: m.env, platform: m.platform, runner: m.runner, setup, prior, report, signal };\n}\n\nconst DEFAULT_SETUP: SetupOptions = { scope: 'project', yes: false, dryRun: false, repair: false };\n\n/** Combine what was observed with what earlier runs recorded. Records can only add warnings or RESTART_REQUIRED, never VERIFIED. */\nexport function applyPersisted(report: IntegrationReport, entry: PersistedIntegration | undefined): IntegrationReport {\n if (!entry) return report;\n const out: IntegrationReport = { ...report, warnings: [...report.warnings], nextSteps: [...report.nextSteps] };\n if (wasInterrupted(entry)) {\n out.warnings.push('A previous setup run was interrupted before it finished.');\n if (!out.nextSteps.includes('devworkspace integrations repair --yes')) out.nextSteps.push('devworkspace integrations repair --yes');\n }\n if ((out.status === 'NOT_INSTALLED' || out.status === 'DEGRADED') && entry.lastError) {\n out.status = 'FAILED';\n out.warnings.push(`The last setup attempt failed: ${entry.lastError}`);\n }\n if (entry.restartRequiredAt && (out.status === 'VERIFIED' || out.status === 'INSTALLED')) {\n out.status = 'RESTART_REQUIRED';\n out.nextSteps.push('Restart Claude Code (or run /reload-plugins) so it loads the new skills/plugins/hooks, then run: devworkspace integrations status --restarted');\n }\n return out;\n}\n\nexport async function inspectAll(ws: Ws, mEnv: ManagerEnv, opts: { deep: boolean; onlyRequired?: boolean } = { deep: false }): Promise<IntegrationReport[]> {\n const m = resolveEnv(mEnv);\n const state = await loadIntegrationsState(ws, m.now().toISOString());\n const list = m.adapters.filter((a) => !opts.onlyRequired || a.required);\n return Promise.all(\n list.map(async (a) => {\n const ctx = ctxFor(m, DEFAULT_SETUP, state.integrations[a.id], () => undefined);\n try {\n return applyPersisted(await a.inspect(ctx, opts.deep), state.integrations[a.id]);\n } catch (e) {\n return {\n id: a.id,\n name: a.name,\n required: a.required,\n status: 'FAILED' as const,\n variant: null,\n scope: null,\n version: null,\n checks: [],\n capabilities: {},\n warnings: [`Inspection failed: ${scrub((e as Error).message)}`],\n nextSteps: ['devworkspace doctor --debug'],\n deep: opts.deep,\n };\n }\n }),\n );\n}\n\n/** Summary used by doctor/status: required integrations only; OmniRoute never counts. */\nexport function summarize(reports: IntegrationReport[]): { complete: boolean; required: number; verified: number; pending: IntegrationReport[] } {\n const required = reports.filter((r) => r.required);\n const pending = required.filter((r) => r.status !== 'VERIFIED');\n return { complete: pending.length === 0, required: required.length, verified: required.length - pending.length, pending };\n}\n\nexport async function acknowledgeRestart(ws: Ws, ids: IntegrationId[], now: string): Promise<void> {\n for (const id of ids) await saveIntegrationEntry(ws, id, { restartRequiredAt: null }, now);\n}\n\nconst describeStep = (s: PlanStep): { description: string; command?: string; level: StepLevel } => ({ description: s.description, command: s.command, level: s.level });\n\nexport async function runSetup(ws: Ws, mEnv: ManagerEnv, setup: SetupOptions, onEvent: (e: ProgressEvent) => void = () => undefined): Promise<SetupResult> {\n const m = resolveEnv(mEnv);\n const events: ProgressEvent[] = [];\n const emit = (e: ProgressEvent): void => {\n events.push(e);\n onEvent(e);\n };\n const ac = new AbortController();\n const disposeCancel = onCleanup(() => ac.abort());\n const wanted = m.adapters.filter((a) => (setup.only ? setup.only.includes(a.id) : REQUIRED_INTEGRATIONS.includes(a.id)));\n const outcomes: IntegrationOutcome[] = [];\n let approvalRequired = false;\n\n try {\n for (const adapter of wanted) {\n const nowIso = (): string => m.now().toISOString();\n const state = await loadIntegrationsState(ws, nowIso());\n const prior = state.integrations[adapter.id];\n const ctx = ctxFor(m, setup, prior, emit, ac.signal);\n emit({ id: adapter.id, step: 'inspect', state: 'start', detail: `checking ${adapter.name}` });\n const before = applyPersisted(await adapter.inspect(ctx, true), prior);\n const outcome = (action: OutcomeAction, report: IntegrationReport, extra: Partial<IntegrationOutcome> = {}): IntegrationOutcome => ({\n id: adapter.id,\n name: adapter.name,\n action,\n before: before.status,\n report,\n plan: [],\n steps: [],\n notes: [],\n ...extra,\n });\n\n if (before.status === 'VERIFIED' && !setup.repair) {\n emit({ id: adapter.id, step: 'inspect', state: 'skip', detail: 'already verified' });\n outcomes.push(outcome('already-verified', before));\n continue;\n }\n if (before.status === 'RESTART_REQUIRED' && !setup.repair) {\n emit({ id: adapter.id, step: 'inspect', state: 'skip', detail: 'installed; waiting for a Claude Code restart' });\n outcomes.push(outcome('awaiting-restart', before));\n continue;\n }\n\n const plan = await adapter.plan(ctx, before);\n if (plan.steps.length === 0) {\n if (plan.blockers.length > 0) {\n emit({ id: adapter.id, step: 'prerequisites', state: 'fail', detail: plan.blockers[0] });\n outcomes.push(outcome('blocked', { ...before, status: 'PREREQUISITES_MISSING', nextSteps: plan.blockers }, { notes: plan.notes }));\n } else {\n outcomes.push(outcome('nothing-to-do', before, { notes: plan.notes }));\n }\n continue;\n }\n const planInfo = plan.steps.map(describeStep);\n if (setup.dryRun) {\n outcomes.push(outcome('dry-run', before, { plan: planInfo, notes: plan.notes }));\n continue;\n }\n const needsConsent = plan.steps.some((s) => s.level !== 'project');\n if (needsConsent && !setup.yes) {\n approvalRequired = true;\n const levels = [...new Set(plan.steps.filter((s) => s.level !== 'project').map((s) => s.level))];\n outcomes.push(\n outcome('approval-required', { ...before, nextSteps: [`Re-run with --yes to approve: ${levels.join(', ')} step(s). Review them first with --dry-run.`, ...before.nextSteps] }, { plan: planInfo, notes: plan.notes }),\n );\n continue;\n }\n\n // ---- execute ----\n const results: StepResult[] = plan.steps.map((s) => ({ id: s.id, description: s.description, command: s.command, level: s.level, state: 'not-run' as const }));\n const managedPrior = prior?.managedPaths ?? [];\n const isManaged = (rel: string): boolean => managedPrior.some((p) => rel === p || rel.startsWith(`${p}/`));\n const managedNew = new Set<string>(managedPrior);\n let restartRequired = false;\n let failure: string | null = null;\n const hints: string[] = [];\n\n await withLock(\n ws.dir,\n async () => {\n await saveIntegrationEntry(ws, adapter.id, { installing: { pid: process.pid, startedAt: nowIso(), step: plan.steps[0]!.id }, lastAttemptAt: nowIso() }, nowIso());\n const snap = await takeSnapshot(m.root, plan.protectPaths, ['.claude/skills'], isManaged);\n for (let i = 0; i < plan.steps.length && !failure; i++) {\n const step = plan.steps[i]!;\n if (ac.signal.aborted) {\n failure = 'cancelled';\n break;\n }\n emit({ id: adapter.id, step: step.id, state: 'start', detail: step.description });\n let out;\n try {\n out = await step.run();\n } catch (e) {\n out = { ok: false, detail: (e as Error).message };\n }\n const violations = await verifySnapshot(snap);\n if (violations.length > 0) {\n await restoreSnapshot(snap, violations);\n out = { ok: false, detail: `Refused: this step would have overwritten your files (${violations.map((v) => `${v.path}: ${v.reason}`).join('; ')}). They were restored.` };\n }\n const res = results[i]!;\n res.state = out.ok ? 'ok' : 'failed';\n res.detail = out.detail ? scrub(out.detail) : undefined;\n if (!out.ok && out.hint) hints.push(out.hint);\n res.requiresApproval = out.requiresApproval;\n emit({ id: adapter.id, step: step.id, state: out.ok ? 'ok' : 'fail', detail: res.detail ?? step.description });\n if (out.ok) {\n if (out.restartRequired) restartRequired = true;\n for (const p of out.managedPaths ?? []) managedNew.add(p);\n } else failure = res.detail ?? 'step failed';\n }\n },\n { name: 'integrations.lock', staleMs: 30 * 60_000, timeoutMs: 10_000 },\n );\n\n const after = await adapterInspectAfter(ws, adapter, m, setup, emit, nowIso());\n await saveIntegrationEntry(\n ws,\n adapter.id,\n {\n installing: null,\n lastError: failure,\n managedPaths: [...managedNew].sort(),\n ...(restartRequired && !failure ? { restartRequiredAt: nowIso() } : {}),\n },\n nowIso(),\n );\n const finalState = (await loadIntegrationsState(ws, nowIso())).integrations[adapter.id] ?? blankEntry();\n let finalReport = applyPersisted(after, finalState);\n if (failure && finalReport.status !== 'VERIFIED') finalReport = { ...finalReport, status: 'FAILED', warnings: [...finalReport.warnings, `Setup stopped: ${failure}`] };\n if (hints.length > 0) finalReport = { ...finalReport, nextSteps: [...hints, ...finalReport.nextSteps] };\n const approvalStep = results.find((r) => r.requiresApproval);\n if (approvalStep?.command) finalReport = { ...finalReport, nextSteps: [`Run this yourself in a terminal (Claude Code needs your approval): ${approvalStep.command}`, ...finalReport.nextSteps] };\n await saveIntegrationEntry(\n ws,\n adapter.id,\n { status: finalReport.status, variant: finalReport.variant, scope: finalReport.scope, version: finalReport.version, lastVerifiedAt: finalReport.status === 'VERIFIED' ? nowIso() : null },\n nowIso(),\n );\n outcomes.push(outcome(failure ? 'failed' : 'installed', finalReport, { plan: planInfo, steps: results, notes: plan.notes }));\n }\n } finally {\n disposeCancel();\n }\n\n const required = outcomes.filter((o) => o.report.required);\n return {\n dryRun: setup.dryRun,\n scope: setup.scope,\n outcomes,\n events,\n complete: required.length > 0 && required.every((o) => o.report.status === 'VERIFIED'),\n approvalRequired,\n };\n}\n\nasync function adapterInspectAfter(ws: Ws, adapter: IntegrationAdapter, m: ReturnType<typeof resolveEnv>, setup: SetupOptions, emit: (e: ProgressEvent) => void, now: string): Promise<IntegrationReport> {\n const state = await loadIntegrationsState(ws, now);\n emit({ id: adapter.id, step: 'verify', state: 'start', detail: 'verifying the installation' });\n const report = await adapter.inspect(ctxFor(m, setup, state.integrations[adapter.id], emit), true);\n emit({ id: adapter.id, step: 'verify', state: report.status === 'VERIFIED' ? 'ok' : 'info', detail: report.status });\n return report;\n}\n"]}
@@ -0,0 +1,13 @@
1
+ export interface NoticeSource {
2
+ name: string;
3
+ repo: string;
4
+ license: string;
5
+ copyright: string;
6
+ /** What DevWorkspace installed from it. */
7
+ installed: string;
8
+ }
9
+ /** Idempotently add (or refresh) one project's notice block. Only text between its own markers is ever replaced. */
10
+ export declare function writeThirdPartyNotice(root: string, source: NoticeSource): Promise<void>;
11
+ export declare const AGENT_SKILLS_NOTICE: NoticeSource;
12
+ export declare const PONYTAIL_NOTICE: NoticeSource;
13
+ export declare const GRAPHIFY_NOTICE: NoticeSource;
@@ -0,0 +1,68 @@
1
+ import path from 'node:path';
2
+ import { promises as fs } from 'node:fs';
3
+ import { atomicWriteFile } from '../utils/fs.js';
4
+ const HEADER = [
5
+ '# Third-party notices',
6
+ '',
7
+ 'DevWorkspace can install the following third-party software into this project or onto this machine.',
8
+ 'DevWorkspace does not vendor or modify their code; files are copied unchanged by their own installers.',
9
+ 'Each project remains under its own license. Keep this notice if you commit the installed files.',
10
+ 'Text outside the marked blocks is yours and is never modified.',
11
+ '',
12
+ ].join('\n');
13
+ const startMarker = (name) => `<!-- devworkspace:notice:start ${name} -->`;
14
+ const endMarker = (name) => `<!-- devworkspace:notice:end ${name} -->`;
15
+ function entry(s) {
16
+ return [
17
+ startMarker(s.name),
18
+ `## ${s.name}`,
19
+ `- Source: https://github.com/${s.repo}`,
20
+ `- License: ${s.license}`,
21
+ `- Copyright: ${s.copyright}`,
22
+ `- Installed by DevWorkspace: ${s.installed}`,
23
+ endMarker(s.name),
24
+ ].join('\n');
25
+ }
26
+ /** Idempotently add (or refresh) one project's notice block. Only text between its own markers is ever replaced. */
27
+ export async function writeThirdPartyNotice(root, source) {
28
+ const file = path.join(root, '.devworkspace', 'THIRD_PARTY_NOTICES.md');
29
+ let text = HEADER;
30
+ try {
31
+ text = await fs.readFile(file, 'utf8');
32
+ }
33
+ catch {
34
+ /* new file */
35
+ }
36
+ const start = text.indexOf(startMarker(source.name));
37
+ const end = text.indexOf(endMarker(source.name));
38
+ const block = entry(source);
39
+ if (start !== -1 && end > start) {
40
+ text = text.slice(0, start) + block + text.slice(end + endMarker(source.name).length);
41
+ }
42
+ else {
43
+ text = text.replace(/\n*$/, '\n\n') + block + '\n';
44
+ }
45
+ await atomicWriteFile(file, text);
46
+ }
47
+ export const AGENT_SKILLS_NOTICE = {
48
+ name: 'Agent Skills',
49
+ repo: 'addyosmani/agent-skills',
50
+ license: 'MIT',
51
+ copyright: 'Copyright (c) 2025 Addy Osmani',
52
+ installed: 'engineering skills copied into .claude/skills/ (portable skills, not the native plugin)',
53
+ };
54
+ export const PONYTAIL_NOTICE = {
55
+ name: 'Ponytail',
56
+ repo: 'DietrichGebert/ponytail',
57
+ license: 'MIT',
58
+ copyright: 'Copyright (c) 2026 DietrichGebert',
59
+ installed: 'skills copied into .claude/skills/ (portable skills, not the native plugin; no hooks, commands or always-on ruleset)',
60
+ };
61
+ export const GRAPHIFY_NOTICE = {
62
+ name: 'Graphify',
63
+ repo: 'Graphify-Labs/graphify',
64
+ license: 'see the project repository and the graphifyy package metadata',
65
+ copyright: 'see the project repository',
66
+ installed: 'the graphifyy command-line tool (user-level) and its project skill in .claude/skills/graphify/',
67
+ };
68
+ //# sourceMappingURL=notices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notices.js","sourceRoot":"","sources":["../../src/integrations/notices.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { promises as fs } from 'node:fs';\nimport { atomicWriteFile } from '../utils/fs.js';\n\nexport interface NoticeSource {\n name: string;\n repo: string;\n license: string;\n copyright: string;\n /** What DevWorkspace installed from it. */\n installed: string;\n}\n\nconst HEADER = [\n '# Third-party notices',\n '',\n 'DevWorkspace can install the following third-party software into this project or onto this machine.',\n 'DevWorkspace does not vendor or modify their code; files are copied unchanged by their own installers.',\n 'Each project remains under its own license. Keep this notice if you commit the installed files.',\n 'Text outside the marked blocks is yours and is never modified.',\n '',\n].join('\\n');\n\nconst startMarker = (name: string): string => `<!-- devworkspace:notice:start ${name} -->`;\nconst endMarker = (name: string): string => `<!-- devworkspace:notice:end ${name} -->`;\n\nfunction entry(s: NoticeSource): string {\n return [\n startMarker(s.name),\n `## ${s.name}`,\n `- Source: https://github.com/${s.repo}`,\n `- License: ${s.license}`,\n `- Copyright: ${s.copyright}`,\n `- Installed by DevWorkspace: ${s.installed}`,\n endMarker(s.name),\n ].join('\\n');\n}\n\n/** Idempotently add (or refresh) one project's notice block. Only text between its own markers is ever replaced. */\nexport async function writeThirdPartyNotice(root: string, source: NoticeSource): Promise<void> {\n const file = path.join(root, '.devworkspace', 'THIRD_PARTY_NOTICES.md');\n let text = HEADER;\n try {\n text = await fs.readFile(file, 'utf8');\n } catch {\n /* new file */\n }\n const start = text.indexOf(startMarker(source.name));\n const end = text.indexOf(endMarker(source.name));\n const block = entry(source);\n if (start !== -1 && end > start) {\n text = text.slice(0, start) + block + text.slice(end + endMarker(source.name).length);\n } else {\n text = text.replace(/\\n*$/, '\\n\\n') + block + '\\n';\n }\n await atomicWriteFile(file, text);\n}\n\nexport const AGENT_SKILLS_NOTICE: NoticeSource = {\n name: 'Agent Skills',\n repo: 'addyosmani/agent-skills',\n license: 'MIT',\n copyright: 'Copyright (c) 2025 Addy Osmani',\n installed: 'engineering skills copied into .claude/skills/ (portable skills, not the native plugin)',\n};\n\nexport const PONYTAIL_NOTICE: NoticeSource = {\n name: 'Ponytail',\n repo: 'DietrichGebert/ponytail',\n license: 'MIT',\n copyright: 'Copyright (c) 2026 DietrichGebert',\n installed: 'skills copied into .claude/skills/ (portable skills, not the native plugin; no hooks, commands or always-on ruleset)',\n};\n\nexport const GRAPHIFY_NOTICE: NoticeSource = {\n name: 'Graphify',\n repo: 'Graphify-Labs/graphify',\n license: 'see the project repository and the graphifyy package metadata',\n copyright: 'see the project repository',\n installed: 'the graphifyy command-line tool (user-level) and its project skill in .claude/skills/graphify/',\n};\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Installers (graphify, claude plugin, skills CLI) edit files the user owns. Before a step runs we snapshot the files
3
+ * it may touch, and afterwards verify that everything the user had is still there. If not, we restore the snapshot.
4
+ */
5
+ export interface Snapshot {
6
+ root: string;
7
+ /** rel path -> original text, or null when the file did not exist. */
8
+ files: Map<string, string | null>;
9
+ /** Existing files below these directories must stay byte-identical. */
10
+ trees: Map<string, string>;
11
+ }
12
+ export interface Violation {
13
+ path: string;
14
+ reason: string;
15
+ }
16
+ export declare function takeSnapshot(root: string, files: string[], trees?: string[], skipInTrees?: (rel: string) => boolean): Promise<Snapshot>;
17
+ /** True if every key/element of `before` is still present in `after` (after may add more). */
18
+ export declare function jsonExtends(before: unknown, after: unknown): boolean;
19
+ export declare function verifySnapshot(snap: Snapshot): Promise<Violation[]>;
20
+ export declare function restoreSnapshot(snap: Snapshot, violations: Violation[]): Promise<void>;
@@ -0,0 +1,131 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { atomicWriteFile } from '../utils/fs.js';
4
+ import { resolveInside } from '../utils/paths.js';
5
+ const MAX_TREE_FILES = 3000;
6
+ const MAX_FILE_BYTES = 2 * 1024 * 1024;
7
+ async function readText(file) {
8
+ try {
9
+ const st = await fs.stat(file);
10
+ if (!st.isFile() || st.size > MAX_FILE_BYTES)
11
+ return null;
12
+ return await fs.readFile(file, 'utf8');
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ async function walk(dir, rel, out, skip) {
19
+ let entries;
20
+ try {
21
+ entries = await fs.readdir(dir, { withFileTypes: true });
22
+ }
23
+ catch {
24
+ return;
25
+ }
26
+ for (const e of entries) {
27
+ if (out.size >= MAX_TREE_FILES)
28
+ return;
29
+ const r = `${rel}/${e.name}`;
30
+ if (e.isSymbolicLink())
31
+ continue;
32
+ if (e.isDirectory())
33
+ await walk(path.join(dir, e.name), r, out, skip);
34
+ else if (e.isFile() && !skip(r)) {
35
+ const text = await readText(path.join(dir, e.name));
36
+ if (text !== null)
37
+ out.set(r, text);
38
+ }
39
+ }
40
+ }
41
+ export async function takeSnapshot(root, files, trees = [], skipInTrees = () => false) {
42
+ const snap = { root, files: new Map(), trees: new Map() };
43
+ for (const rel of files)
44
+ snap.files.set(rel, await readText(resolveInside(root, rel)));
45
+ for (const t of trees) {
46
+ const found = new Map();
47
+ await walk(resolveInside(root, t), t, found, skipInTrees);
48
+ for (const [k, v] of found)
49
+ snap.trees.set(k, v);
50
+ }
51
+ return snap;
52
+ }
53
+ function lineSubsequence(before, after) {
54
+ const norm = (s) => s.replace(/\r\n/g, '\n').split('\n').map((l) => l.trimEnd()).filter((l) => l.trim() !== '');
55
+ const a = norm(after);
56
+ let i = 0;
57
+ for (const line of norm(before)) {
58
+ while (i < a.length && a[i] !== line)
59
+ i++;
60
+ if (i >= a.length)
61
+ return false;
62
+ i++;
63
+ }
64
+ return true;
65
+ }
66
+ function deepEqual(a, b) {
67
+ return JSON.stringify(a) === JSON.stringify(b);
68
+ }
69
+ /** True if every key/element of `before` is still present in `after` (after may add more). */
70
+ export function jsonExtends(before, after) {
71
+ if (Array.isArray(before))
72
+ return Array.isArray(after) && before.every((b) => after.some((a) => jsonExtends(b, a)));
73
+ if (typeof before === 'object' && before !== null) {
74
+ if (typeof after !== 'object' || after === null || Array.isArray(after))
75
+ return false;
76
+ return Object.entries(before).every(([k, v]) => k in after && jsonExtends(v, after[k]));
77
+ }
78
+ return deepEqual(before, after);
79
+ }
80
+ function preserved(rel, before, after) {
81
+ if (rel.endsWith('.json')) {
82
+ let b;
83
+ try {
84
+ b = JSON.parse(before);
85
+ }
86
+ catch {
87
+ return before === after ? null : 'the original file was not valid JSON and was modified';
88
+ }
89
+ let a;
90
+ try {
91
+ a = JSON.parse(after);
92
+ }
93
+ catch {
94
+ return 'the file is no longer valid JSON';
95
+ }
96
+ return jsonExtends(b, a) ? null : 'existing settings were changed or removed';
97
+ }
98
+ return lineSubsequence(before, after) ? null : 'existing lines were changed or removed';
99
+ }
100
+ export async function verifySnapshot(snap) {
101
+ const bad = [];
102
+ for (const [rel, before] of snap.files) {
103
+ const after = await readText(resolveInside(snap.root, rel));
104
+ if (before === null)
105
+ continue; // files that did not exist may be created
106
+ if (after === null)
107
+ bad.push({ path: rel, reason: 'the file was deleted' });
108
+ else {
109
+ const why = preserved(rel, before, after);
110
+ if (why)
111
+ bad.push({ path: rel, reason: why });
112
+ }
113
+ }
114
+ for (const [rel, before] of snap.trees) {
115
+ const after = await readText(resolveInside(snap.root, rel));
116
+ if (after === null)
117
+ bad.push({ path: rel, reason: 'the file was deleted' });
118
+ else if (after !== before)
119
+ bad.push({ path: rel, reason: 'the file was overwritten' });
120
+ }
121
+ return bad;
122
+ }
123
+ export async function restoreSnapshot(snap, violations) {
124
+ for (const v of violations) {
125
+ const original = snap.files.get(v.path) ?? snap.trees.get(v.path);
126
+ if (original === undefined || original === null)
127
+ continue;
128
+ await atomicWriteFile(resolveInside(snap.root, v.path), original);
129
+ }
130
+ }
131
+ //# sourceMappingURL=protect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protect.js","sourceRoot":"","sources":["../../src/integrations/protect.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { atomicWriteFile } from '../utils/fs.js';\nimport { resolveInside } from '../utils/paths.js';\n\n/**\n * Installers (graphify, claude plugin, skills CLI) edit files the user owns. Before a step runs we snapshot the files\n * it may touch, and afterwards verify that everything the user had is still there. If not, we restore the snapshot.\n */\nexport interface Snapshot {\n root: string;\n /** rel path -> original text, or null when the file did not exist. */\n files: Map<string, string | null>;\n /** Existing files below these directories must stay byte-identical. */\n trees: Map<string, string>;\n}\n\nexport interface Violation {\n path: string;\n reason: string;\n}\n\nconst MAX_TREE_FILES = 3000;\nconst MAX_FILE_BYTES = 2 * 1024 * 1024;\n\nasync function readText(file: string): Promise<string | null> {\n try {\n const st = await fs.stat(file);\n if (!st.isFile() || st.size > MAX_FILE_BYTES) return null;\n return await fs.readFile(file, 'utf8');\n } catch {\n return null;\n }\n}\n\nasync function walk(dir: string, rel: string, out: Map<string, string>, skip: (rel: string) => boolean): Promise<void> {\n let entries;\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n if (out.size >= MAX_TREE_FILES) return;\n const r = `${rel}/${e.name}`;\n if (e.isSymbolicLink()) continue;\n if (e.isDirectory()) await walk(path.join(dir, e.name), r, out, skip);\n else if (e.isFile() && !skip(r)) {\n const text = await readText(path.join(dir, e.name));\n if (text !== null) out.set(r, text);\n }\n }\n}\n\nexport async function takeSnapshot(root: string, files: string[], trees: string[] = [], skipInTrees: (rel: string) => boolean = () => false): Promise<Snapshot> {\n const snap: Snapshot = { root, files: new Map(), trees: new Map() };\n for (const rel of files) snap.files.set(rel, await readText(resolveInside(root, rel)));\n for (const t of trees) {\n const found = new Map<string, string>();\n await walk(resolveInside(root, t), t, found, skipInTrees);\n for (const [k, v] of found) snap.trees.set(k, v);\n }\n return snap;\n}\n\nfunction lineSubsequence(before: string, after: string): boolean {\n const norm = (s: string): string[] => s.replace(/\\r\\n/g, '\\n').split('\\n').map((l) => l.trimEnd()).filter((l) => l.trim() !== '');\n const a = norm(after);\n let i = 0;\n for (const line of norm(before)) {\n while (i < a.length && a[i] !== line) i++;\n if (i >= a.length) return false;\n i++;\n }\n return true;\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n/** True if every key/element of `before` is still present in `after` (after may add more). */\nexport function jsonExtends(before: unknown, after: unknown): boolean {\n if (Array.isArray(before)) return Array.isArray(after) && before.every((b) => after.some((a) => jsonExtends(b, a)));\n if (typeof before === 'object' && before !== null) {\n if (typeof after !== 'object' || after === null || Array.isArray(after)) return false;\n return Object.entries(before as Record<string, unknown>).every(([k, v]) => k in (after as Record<string, unknown>) && jsonExtends(v, (after as Record<string, unknown>)[k]));\n }\n return deepEqual(before, after);\n}\n\nfunction preserved(rel: string, before: string, after: string): string | null {\n if (rel.endsWith('.json')) {\n let b: unknown;\n try {\n b = JSON.parse(before);\n } catch {\n return before === after ? null : 'the original file was not valid JSON and was modified';\n }\n let a: unknown;\n try {\n a = JSON.parse(after);\n } catch {\n return 'the file is no longer valid JSON';\n }\n return jsonExtends(b, a) ? null : 'existing settings were changed or removed';\n }\n return lineSubsequence(before, after) ? null : 'existing lines were changed or removed';\n}\n\nexport async function verifySnapshot(snap: Snapshot): Promise<Violation[]> {\n const bad: Violation[] = [];\n for (const [rel, before] of snap.files) {\n const after = await readText(resolveInside(snap.root, rel));\n if (before === null) continue; // files that did not exist may be created\n if (after === null) bad.push({ path: rel, reason: 'the file was deleted' });\n else {\n const why = preserved(rel, before, after);\n if (why) bad.push({ path: rel, reason: why });\n }\n }\n for (const [rel, before] of snap.trees) {\n const after = await readText(resolveInside(snap.root, rel));\n if (after === null) bad.push({ path: rel, reason: 'the file was deleted' });\n else if (after !== before) bad.push({ path: rel, reason: 'the file was overwritten' });\n }\n return bad;\n}\n\nexport async function restoreSnapshot(snap: Snapshot, violations: Violation[]): Promise<void> {\n for (const v of violations) {\n const original = snap.files.get(v.path) ?? snap.trees.get(v.path);\n if (original === undefined || original === null) continue;\n await atomicWriteFile(resolveInside(snap.root, v.path), original);\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { Ws } from '../core/workspace.js';
2
+ import { type IntegrationId, type IntegrationsStateFile, type PersistedIntegration } from './types.js';
3
+ export declare function emptyState(now: string): IntegrationsStateFile;
4
+ export declare function validateIntegrationsState(v: unknown, file?: string): IntegrationsStateFile;
5
+ /** The state file is a hint, never proof: adapters re-observe the real installation on every run. */
6
+ export declare function loadIntegrationsState(ws: Ws, now: string): Promise<IntegrationsStateFile>;
7
+ export declare function blankEntry(): PersistedIntegration;
8
+ export declare function saveIntegrationEntry(ws: Ws, id: IntegrationId, patch: Partial<PersistedIntegration>, now: string): Promise<void>;
9
+ /** True when a previous setup run died mid-way (its pid is gone). */
10
+ export declare function wasInterrupted(entry: PersistedIntegration | undefined): boolean;