devworkspace 0.1.1 → 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.
- package/CHANGELOG.md +20 -0
- package/README.md +15 -2
- package/dist/agents/claude/content.js +3 -3
- package/dist/agents/claude/content.js.map +1 -1
- package/dist/cli/args.d.ts +19 -0
- package/dist/cli/args.js +6 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/help.js +40 -5
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/render.d.ts +7 -0
- package/dist/cli/render.js +96 -13
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/run.d.ts +6 -0
- package/dist/cli/run.js +65 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/core/workspace.js +2 -1
- package/dist/core/workspace.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/adapters/agent-skills.d.ts +8 -0
- package/dist/integrations/adapters/agent-skills.js +25 -0
- package/dist/integrations/adapters/agent-skills.js.map +1 -0
- package/dist/integrations/adapters/claudePlugin.d.ts +41 -0
- package/dist/integrations/adapters/claudePlugin.js +129 -0
- package/dist/integrations/adapters/claudePlugin.js.map +1 -0
- package/dist/integrations/adapters/graphify.d.ts +2 -0
- package/dist/integrations/adapters/graphify.js +239 -0
- package/dist/integrations/adapters/graphify.js.map +1 -0
- package/dist/integrations/adapters/omniroute.d.ts +6 -0
- package/dist/integrations/adapters/omniroute.js +32 -0
- package/dist/integrations/adapters/omniroute.js.map +1 -0
- package/dist/integrations/adapters/pluginSkill.d.ts +28 -0
- package/dist/integrations/adapters/pluginSkill.js +195 -0
- package/dist/integrations/adapters/pluginSkill.js.map +1 -0
- package/dist/integrations/adapters/ponytail.d.ts +8 -0
- package/dist/integrations/adapters/ponytail.js +33 -0
- package/dist/integrations/adapters/ponytail.js.map +1 -0
- package/dist/integrations/exec.d.ts +26 -0
- package/dist/integrations/exec.js +167 -0
- package/dist/integrations/exec.js.map +1 -0
- package/dist/integrations/index.d.ts +5 -6
- package/dist/integrations/index.js +3 -29
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/manager.d.ts +62 -0
- package/dist/integrations/manager.js +248 -0
- package/dist/integrations/manager.js.map +1 -0
- package/dist/integrations/notices.d.ts +13 -0
- package/dist/integrations/notices.js +68 -0
- package/dist/integrations/notices.js.map +1 -0
- package/dist/integrations/protect.d.ts +20 -0
- package/dist/integrations/protect.js +131 -0
- package/dist/integrations/protect.js.map +1 -0
- package/dist/integrations/state.d.ts +10 -0
- package/dist/integrations/state.js +81 -0
- package/dist/integrations/state.js.map +1 -0
- package/dist/integrations/types.d.ts +133 -16
- package/dist/integrations/types.js +13 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/prompts/onboarding.d.ts +3 -3
- package/dist/prompts/onboarding.js +5 -4
- package/dist/prompts/onboarding.js.map +1 -1
- package/dist/services/analysis.d.ts +2 -2
- package/dist/services/analysis.js +4 -5
- package/dist/services/analysis.js.map +1 -1
- package/dist/services/common.d.ts +8 -5
- package/dist/services/common.js +2 -5
- package/dist/services/common.js.map +1 -1
- package/dist/services/doctor.d.ts +10 -0
- package/dist/services/doctor.js +9 -15
- package/dist/services/doctor.js.map +1 -1
- package/dist/services/init.d.ts +9 -2
- package/dist/services/init.js +10 -7
- package/dist/services/init.js.map +1 -1
- package/dist/services/integrations.d.ts +26 -0
- package/dist/services/integrations.js +35 -0
- package/dist/services/integrations.js.map +1 -0
- package/dist/services/status.d.ts +3 -2
- package/dist/services/status.js +3 -3
- package/dist/services/status.js.map +1 -1
- package/dist/utils/lock.d.ts +2 -0
- package/dist/utils/lock.js +2 -2
- package/dist/utils/lock.js.map +1 -1
- package/dist/utils/proc.d.ts +8 -1
- package/dist/utils/proc.js +31 -8
- package/dist/utils/proc.js.map +1 -1
- package/docs/architecture.md +1 -1
- package/docs/claude.md +4 -0
- package/docs/getting-started.md +2 -2
- package/docs/integrations.md +98 -33
- package/docs/security.md +5 -1
- package/docs/testing.md +2 -0
- package/docs/troubleshooting.md +8 -1
- package/package.json +1 -1
- package/dist/integrations/agent-skills.d.ts +0 -10
- package/dist/integrations/agent-skills.js +0 -45
- package/dist/integrations/agent-skills.js.map +0 -1
- package/dist/integrations/graphify.d.ts +0 -7
- package/dist/integrations/graphify.js +0 -54
- package/dist/integrations/graphify.js.map +0 -1
- package/dist/integrations/omniroute.d.ts +0 -7
- package/dist/integrations/omniroute.js +0 -24
- package/dist/integrations/omniroute.js.map +0 -1
- package/dist/integrations/ponytail.d.ts +0 -10
- package/dist/integrations/ponytail.js +0 -58
- package/dist/integrations/ponytail.js.map +0 -1
- package/dist/integrations/probe.d.ts +0 -6
- package/dist/integrations/probe.js +0 -41
- package/dist/integrations/probe.js.map +0 -1
|
@@ -1,30 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { graphify } from './graphify.js';
|
|
5
|
-
import { omniroute } from './omniroute.js';
|
|
6
|
-
import { ponytail } from './ponytail.js';
|
|
7
|
-
export const INTEGRATIONS = [graphify, agentSkills, ponytail, omniroute];
|
|
8
|
-
export function defaultIntegrationContext(root, env = process.env, home = os.homedir()) {
|
|
9
|
-
return { root, home, env, which: (n) => which(n, env) };
|
|
10
|
-
}
|
|
11
|
-
/** Detect every integration. A failing adapter degrades to an ERROR status instead of breaking the workspace. */
|
|
12
|
-
export async function detectIntegrations(ctx, list = INTEGRATIONS) {
|
|
13
|
-
return Promise.all(list.map(async (i) => {
|
|
14
|
-
try {
|
|
15
|
-
return await i.detect(ctx);
|
|
16
|
-
}
|
|
17
|
-
catch (e) {
|
|
18
|
-
return {
|
|
19
|
-
id: i.id,
|
|
20
|
-
name: i.name,
|
|
21
|
-
optional: i.optional,
|
|
22
|
-
state: 'ERROR',
|
|
23
|
-
details: [`detection failed: ${e.message}`],
|
|
24
|
-
remediation: ['Run "devworkspace doctor --debug" for details.'],
|
|
25
|
-
docsUrl: '',
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
}));
|
|
29
|
-
}
|
|
1
|
+
export { ADAPTERS, acknowledgeRestart, applyPersisted, inspectAll, runSetup, summarize } from './manager.js';
|
|
2
|
+
export { createRunner } from './exec.js';
|
|
3
|
+
export { INTEGRATION_IDS, REQUIRED_INTEGRATIONS, STATUS_CODES } from './types.js';
|
|
30
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7G,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { ADAPTERS, acknowledgeRestart, applyPersisted, inspectAll, runSetup, summarize } from './manager.js';\nexport type { IntegrationOutcome, ManagerEnv, OutcomeAction, SetupResult, StepResult } from './manager.js';\nexport { createRunner } from './exec.js';\nexport { INTEGRATION_IDS, REQUIRED_INTEGRATIONS, STATUS_CODES } from './types.js';\nexport type { IntegrationId, IntegrationReport, InstallScope, ProgressEvent, Runner, SetupOptions, StatusCode } from './types.js';\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Ws } from '../core/workspace.js';
|
|
2
|
+
import { type IntegrationAdapter, type IntegrationId, type IntegrationReport, type PersistedIntegration, type ProgressEvent, type Runner, type SetupOptions, type StatusCode, type StepLevel } from './types.js';
|
|
3
|
+
/** Tool output goes into reports and the state file: strip secrets and machine-specific paths, flatten to one line. */
|
|
4
|
+
export declare function scrub(text: string): string;
|
|
5
|
+
export declare const ADAPTERS: IntegrationAdapter[];
|
|
6
|
+
export interface ManagerEnv {
|
|
7
|
+
root: string;
|
|
8
|
+
home?: string;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
platform?: NodeJS.Platform;
|
|
11
|
+
runner?: Runner;
|
|
12
|
+
now?: () => Date;
|
|
13
|
+
adapters?: IntegrationAdapter[];
|
|
14
|
+
}
|
|
15
|
+
export interface StepResult {
|
|
16
|
+
id: string;
|
|
17
|
+
description: string;
|
|
18
|
+
command?: string;
|
|
19
|
+
level: StepLevel;
|
|
20
|
+
state: 'ok' | 'failed' | 'not-run';
|
|
21
|
+
detail?: string;
|
|
22
|
+
requiresApproval?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export type OutcomeAction = 'already-verified' | 'awaiting-restart' | 'nothing-to-do' | 'blocked' | 'dry-run' | 'approval-required' | 'installed' | 'failed' | 'not-selected';
|
|
25
|
+
export interface IntegrationOutcome {
|
|
26
|
+
id: IntegrationId;
|
|
27
|
+
name: string;
|
|
28
|
+
action: OutcomeAction;
|
|
29
|
+
before: StatusCode;
|
|
30
|
+
report: IntegrationReport;
|
|
31
|
+
plan: {
|
|
32
|
+
description: string;
|
|
33
|
+
command?: string;
|
|
34
|
+
level: StepLevel;
|
|
35
|
+
}[];
|
|
36
|
+
steps: StepResult[];
|
|
37
|
+
notes: string[];
|
|
38
|
+
}
|
|
39
|
+
export interface SetupResult {
|
|
40
|
+
dryRun: boolean;
|
|
41
|
+
scope: SetupOptions['scope'];
|
|
42
|
+
outcomes: IntegrationOutcome[];
|
|
43
|
+
events: ProgressEvent[];
|
|
44
|
+
/** Every requested required integration is VERIFIED. */
|
|
45
|
+
complete: boolean;
|
|
46
|
+
approvalRequired: boolean;
|
|
47
|
+
}
|
|
48
|
+
/** Combine what was observed with what earlier runs recorded. Records can only add warnings or RESTART_REQUIRED, never VERIFIED. */
|
|
49
|
+
export declare function applyPersisted(report: IntegrationReport, entry: PersistedIntegration | undefined): IntegrationReport;
|
|
50
|
+
export declare function inspectAll(ws: Ws, mEnv: ManagerEnv, opts?: {
|
|
51
|
+
deep: boolean;
|
|
52
|
+
onlyRequired?: boolean;
|
|
53
|
+
}): Promise<IntegrationReport[]>;
|
|
54
|
+
/** Summary used by doctor/status: required integrations only; OmniRoute never counts. */
|
|
55
|
+
export declare function summarize(reports: IntegrationReport[]): {
|
|
56
|
+
complete: boolean;
|
|
57
|
+
required: number;
|
|
58
|
+
verified: number;
|
|
59
|
+
pending: IntegrationReport[];
|
|
60
|
+
};
|
|
61
|
+
export declare function acknowledgeRestart(ws: Ws, ids: IntegrationId[], now: string): Promise<void>;
|
|
62
|
+
export declare function runSetup(ws: Ws, mEnv: ManagerEnv, setup: SetupOptions, onEvent?: (e: ProgressEvent) => void): Promise<SetupResult>;
|
|
@@ -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>;
|