devflow-kit 1.7.0 → 1.8.1
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 +50 -0
- package/README.md +31 -1
- package/dist/cli.js +3 -1
- package/dist/commands/hud.d.ts +22 -0
- package/dist/commands/hud.js +180 -0
- package/dist/commands/init.d.ts +2 -12
- package/dist/commands/init.js +362 -181
- package/dist/commands/list.js +1 -0
- package/dist/commands/uninstall.js +9 -16
- package/dist/hud/cache.d.ts +14 -0
- package/dist/hud/cache.js +54 -0
- package/dist/hud/colors.d.ts +23 -0
- package/dist/hud/colors.js +62 -0
- package/dist/hud/components/config-counts.d.ts +8 -0
- package/dist/hud/components/config-counts.js +91 -0
- package/dist/hud/components/context-usage.d.ts +8 -0
- package/dist/hud/components/context-usage.js +49 -0
- package/dist/hud/components/diff-stats.d.ts +3 -0
- package/dist/hud/components/diff-stats.js +36 -0
- package/dist/hud/components/directory.d.ts +3 -0
- package/dist/hud/components/directory.js +10 -0
- package/dist/hud/components/git-ahead-behind.d.ts +3 -0
- package/dist/hud/components/git-ahead-behind.js +16 -0
- package/dist/hud/components/git-branch.d.ts +3 -0
- package/dist/hud/components/git-branch.js +14 -0
- package/dist/hud/components/model.d.ts +3 -0
- package/dist/hud/components/model.js +21 -0
- package/dist/hud/components/release-info.d.ts +3 -0
- package/dist/hud/components/release-info.js +9 -0
- package/dist/hud/components/session-cost.d.ts +3 -0
- package/dist/hud/components/session-cost.js +9 -0
- package/dist/hud/components/session-duration.d.ts +3 -0
- package/dist/hud/components/session-duration.js +18 -0
- package/dist/hud/components/todo-progress.d.ts +3 -0
- package/dist/hud/components/todo-progress.js +11 -0
- package/dist/hud/components/usage-quota.d.ts +3 -0
- package/dist/hud/components/usage-quota.js +45 -0
- package/dist/hud/components/version-badge.d.ts +3 -0
- package/dist/hud/components/version-badge.js +80 -0
- package/dist/hud/components/worktree-count.d.ts +3 -0
- package/dist/hud/components/worktree-count.js +8 -0
- package/dist/hud/config.d.ts +10 -0
- package/dist/hud/config.js +55 -0
- package/dist/hud/credentials.d.ts +17 -0
- package/dist/hud/credentials.js +99 -0
- package/dist/hud/git.d.ts +7 -0
- package/dist/hud/git.js +153 -0
- package/dist/hud/index.d.ts +2 -0
- package/dist/hud/index.js +81 -0
- package/dist/hud/render.d.ts +7 -0
- package/dist/hud/render.js +111 -0
- package/dist/hud/stdin.d.ts +6 -0
- package/dist/hud/stdin.js +25 -0
- package/dist/hud/transcript.d.ts +7 -0
- package/dist/hud/transcript.js +130 -0
- package/dist/hud/types.d.ts +116 -0
- package/dist/hud/types.js +2 -0
- package/dist/hud/usage-api.d.ts +7 -0
- package/dist/hud/usage-api.js +84 -0
- package/dist/utils/manifest.d.ts +1 -0
- package/dist/utils/post-install.d.ts +8 -1
- package/dist/utils/post-install.js +41 -0
- package/package.json +5 -2
- package/plugins/devflow-accessibility/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-ambient/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-ambient/agents/scrutinizer.md +6 -4
- package/plugins/devflow-ambient/agents/shepherd.md +26 -12
- package/plugins/devflow-ambient/agents/simplifier.md +24 -15
- package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-core-skills/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-go/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-implement/agents/scrutinizer.md +6 -4
- package/plugins/devflow-implement/agents/shepherd.md +26 -12
- package/plugins/devflow-implement/agents/simplifier.md +24 -15
- package/plugins/devflow-implement/skills/self-review/references/stub-detection.md +135 -0
- package/plugins/devflow-java/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-python/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-react/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-resolve/agents/simplifier.md +24 -15
- package/plugins/devflow-rust/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-self-review/agents/scrutinizer.md +6 -4
- package/plugins/devflow-self-review/agents/simplifier.md +24 -15
- package/plugins/devflow-self-review/skills/self-review/references/stub-detection.md +135 -0
- package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-typescript/.claude-plugin/plugin.json +1 -1
- package/scripts/hud/cache.d.ts +14 -0
- package/scripts/hud/cache.d.ts.map +1 -0
- package/scripts/hud/cache.js +54 -0
- package/scripts/hud/cache.js.map +1 -0
- package/scripts/hud/colors.d.ts +23 -0
- package/scripts/hud/colors.d.ts.map +1 -0
- package/scripts/hud/colors.js +62 -0
- package/scripts/hud/colors.js.map +1 -0
- package/scripts/hud/components/config-counts.d.ts +8 -0
- package/scripts/hud/components/config-counts.d.ts.map +1 -0
- package/scripts/hud/components/config-counts.js +91 -0
- package/scripts/hud/components/config-counts.js.map +1 -0
- package/scripts/hud/components/context-usage.d.ts +8 -0
- package/scripts/hud/components/context-usage.d.ts.map +1 -0
- package/scripts/hud/components/context-usage.js +49 -0
- package/scripts/hud/components/context-usage.js.map +1 -0
- package/scripts/hud/components/diff-stats.d.ts +3 -0
- package/scripts/hud/components/diff-stats.d.ts.map +1 -0
- package/scripts/hud/components/diff-stats.js +36 -0
- package/scripts/hud/components/diff-stats.js.map +1 -0
- package/scripts/hud/components/directory.d.ts +3 -0
- package/scripts/hud/components/directory.d.ts.map +1 -0
- package/scripts/hud/components/directory.js +10 -0
- package/scripts/hud/components/directory.js.map +1 -0
- package/scripts/hud/components/git-ahead-behind.d.ts +3 -0
- package/scripts/hud/components/git-ahead-behind.d.ts.map +1 -0
- package/scripts/hud/components/git-ahead-behind.js +16 -0
- package/scripts/hud/components/git-ahead-behind.js.map +1 -0
- package/scripts/hud/components/git-branch.d.ts +3 -0
- package/scripts/hud/components/git-branch.d.ts.map +1 -0
- package/scripts/hud/components/git-branch.js +14 -0
- package/scripts/hud/components/git-branch.js.map +1 -0
- package/scripts/hud/components/model.d.ts +3 -0
- package/scripts/hud/components/model.d.ts.map +1 -0
- package/scripts/hud/components/model.js +21 -0
- package/scripts/hud/components/model.js.map +1 -0
- package/scripts/hud/components/release-info.d.ts +3 -0
- package/scripts/hud/components/release-info.d.ts.map +1 -0
- package/scripts/hud/components/release-info.js +9 -0
- package/scripts/hud/components/release-info.js.map +1 -0
- package/scripts/hud/components/session-cost.d.ts +3 -0
- package/scripts/hud/components/session-cost.d.ts.map +1 -0
- package/scripts/hud/components/session-cost.js +9 -0
- package/scripts/hud/components/session-cost.js.map +1 -0
- package/scripts/hud/components/session-duration.d.ts +3 -0
- package/scripts/hud/components/session-duration.d.ts.map +1 -0
- package/scripts/hud/components/session-duration.js +18 -0
- package/scripts/hud/components/session-duration.js.map +1 -0
- package/scripts/hud/components/todo-progress.d.ts +3 -0
- package/scripts/hud/components/todo-progress.d.ts.map +1 -0
- package/scripts/hud/components/todo-progress.js +11 -0
- package/scripts/hud/components/todo-progress.js.map +1 -0
- package/scripts/hud/components/usage-quota.d.ts +3 -0
- package/scripts/hud/components/usage-quota.d.ts.map +1 -0
- package/scripts/hud/components/usage-quota.js +45 -0
- package/scripts/hud/components/usage-quota.js.map +1 -0
- package/scripts/hud/components/version-badge.d.ts +3 -0
- package/scripts/hud/components/version-badge.d.ts.map +1 -0
- package/scripts/hud/components/version-badge.js +80 -0
- package/scripts/hud/components/version-badge.js.map +1 -0
- package/scripts/hud/components/worktree-count.d.ts +3 -0
- package/scripts/hud/components/worktree-count.d.ts.map +1 -0
- package/scripts/hud/components/worktree-count.js +8 -0
- package/scripts/hud/components/worktree-count.js.map +1 -0
- package/scripts/hud/config.d.ts +10 -0
- package/scripts/hud/config.d.ts.map +1 -0
- package/scripts/hud/config.js +55 -0
- package/scripts/hud/config.js.map +1 -0
- package/scripts/hud/credentials.d.ts +17 -0
- package/scripts/hud/credentials.d.ts.map +1 -0
- package/scripts/hud/credentials.js +99 -0
- package/scripts/hud/credentials.js.map +1 -0
- package/scripts/hud/git.d.ts +7 -0
- package/scripts/hud/git.d.ts.map +1 -0
- package/scripts/hud/git.js +153 -0
- package/scripts/hud/git.js.map +1 -0
- package/scripts/hud/index.d.ts +2 -0
- package/scripts/hud/index.d.ts.map +1 -0
- package/scripts/hud/index.js +81 -0
- package/scripts/hud/index.js.map +1 -0
- package/scripts/hud/render.d.ts +7 -0
- package/scripts/hud/render.d.ts.map +1 -0
- package/scripts/hud/render.js +111 -0
- package/scripts/hud/render.js.map +1 -0
- package/scripts/hud/stdin.d.ts +6 -0
- package/scripts/hud/stdin.d.ts.map +1 -0
- package/scripts/hud/stdin.js +25 -0
- package/scripts/hud/stdin.js.map +1 -0
- package/scripts/hud/transcript.d.ts +7 -0
- package/scripts/hud/transcript.d.ts.map +1 -0
- package/scripts/hud/transcript.js +130 -0
- package/scripts/hud/transcript.js.map +1 -0
- package/scripts/hud/types.d.ts +116 -0
- package/scripts/hud/types.d.ts.map +1 -0
- package/scripts/hud/types.js +2 -0
- package/scripts/hud/types.js.map +1 -0
- package/scripts/hud/usage-api.d.ts +7 -0
- package/scripts/hud/usage-api.d.ts.map +1 -0
- package/scripts/hud/usage-api.js +84 -0
- package/scripts/hud/usage-api.js.map +1 -0
- package/scripts/hud.sh +5 -0
- package/shared/agents/scrutinizer.md +6 -4
- package/shared/agents/shepherd.md +26 -12
- package/shared/agents/simplifier.md +24 -15
- package/shared/skills/self-review/references/stub-detection.md +135 -0
- package/src/templates/settings.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -8,18 +8,21 @@ import color from 'picocolors';
|
|
|
8
8
|
import { getInstallationPaths } from '../utils/paths.js';
|
|
9
9
|
import { getGitRoot } from '../utils/git.js';
|
|
10
10
|
import { isClaudeCliAvailable } from '../utils/cli.js';
|
|
11
|
-
import { installViaCli, installViaFileCopy } from '../utils/installer.js';
|
|
12
|
-
import { installSettings, installManagedSettings, installClaudeignore, updateGitignore, createDocsStructure, createMemoryDir, migrateMemoryFiles, } from '../utils/post-install.js';
|
|
11
|
+
import { installViaCli, installViaFileCopy, copyDirectory } from '../utils/installer.js';
|
|
12
|
+
import { installSettings, installManagedSettings, installClaudeignore, discoverProjectGitRoots, updateGitignore, createDocsStructure, createMemoryDir, migrateMemoryFiles, } from '../utils/post-install.js';
|
|
13
13
|
import { DEVFLOW_PLUGINS, LEGACY_SKILL_NAMES, LEGACY_COMMAND_NAMES, buildAssetMaps } from '../plugins.js';
|
|
14
14
|
import { detectPlatform, detectShell, getProfilePath, getSafeDeleteInfo, hasSafeDelete } from '../utils/safe-delete.js';
|
|
15
15
|
import { generateSafeDeleteBlock, installToProfile, removeFromProfile, getInstalledVersion, SAFE_DELETE_BLOCK_VERSION } from '../utils/safe-delete-install.js';
|
|
16
16
|
import { addAmbientHook } from './ambient.js';
|
|
17
17
|
import { addMemoryHooks, removeMemoryHooks } from './memory.js';
|
|
18
|
+
import { addHudStatusLine, removeHudStatusLine } from './hud.js';
|
|
19
|
+
import { loadConfig as loadHudConfig, saveConfig as saveHudConfig } from '../hud/config.js';
|
|
18
20
|
import { readManifest, writeManifest, resolvePluginList, detectUpgrade } from '../utils/manifest.js';
|
|
19
21
|
// Re-export pure functions for tests (canonical source is post-install.ts)
|
|
20
|
-
export { substituteSettingsTemplate, computeGitignoreAppend, applyTeamsConfig, stripTeamsConfig, mergeDenyList } from '../utils/post-install.js';
|
|
22
|
+
export { substituteSettingsTemplate, computeGitignoreAppend, applyTeamsConfig, stripTeamsConfig, mergeDenyList, discoverProjectGitRoots } from '../utils/post-install.js';
|
|
21
23
|
export { addAmbientHook, removeAmbientHook, hasAmbientHook } from './ambient.js';
|
|
22
24
|
export { addMemoryHooks, removeMemoryHooks, hasMemoryHooks } from './memory.js';
|
|
25
|
+
export { addHudStatusLine, removeHudStatusLine, hasHudStatusLine } from './hud.js';
|
|
23
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
27
|
const __dirname = dirname(__filename);
|
|
25
28
|
/**
|
|
@@ -35,26 +38,6 @@ export function parsePluginSelection(input, validPlugins) {
|
|
|
35
38
|
const invalid = selected.filter(p => !validNames.includes(p));
|
|
36
39
|
return { selected, invalid };
|
|
37
40
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Build the list of configuration extras available for the given scope/git context.
|
|
40
|
-
* Pure function — no I/O, no side effects.
|
|
41
|
-
*/
|
|
42
|
-
export function buildExtrasOptions(scope, gitRoot) {
|
|
43
|
-
const options = [
|
|
44
|
-
{ value: 'settings', label: 'Settings & Working Memory', hint: 'Model defaults, session memory hooks, status line' },
|
|
45
|
-
];
|
|
46
|
-
if (gitRoot) {
|
|
47
|
-
options.push({ value: 'claudeignore', label: '.claudeignore', hint: 'Exclude secrets, deps, build artifacts from Claude context' });
|
|
48
|
-
}
|
|
49
|
-
if (scope === 'local' && gitRoot) {
|
|
50
|
-
options.push({ value: 'gitignore', label: '.gitignore entries', hint: 'Add .claude/ and .devflow/ to .gitignore' });
|
|
51
|
-
}
|
|
52
|
-
if (scope === 'local') {
|
|
53
|
-
options.push({ value: 'docs', label: '.docs/ directory', hint: 'Review reports, dev logs, status tracking for this project' });
|
|
54
|
-
}
|
|
55
|
-
options.push({ value: 'safe-delete', label: 'Safe-delete (rm → trash)', hint: 'Override rm to use trash CLI — prevents accidental deletion' });
|
|
56
|
-
return options;
|
|
57
|
-
}
|
|
58
41
|
export const initCommand = new Command('init')
|
|
59
42
|
.description('Initialize DevFlow for Claude Code')
|
|
60
43
|
.option('--scope <type>', 'Installation scope: user or local (project-only)', /^(user|local)$/i)
|
|
@@ -66,6 +49,9 @@ export const initCommand = new Command('init')
|
|
|
66
49
|
.option('--no-ambient', 'Disable ambient mode')
|
|
67
50
|
.option('--memory', 'Enable working memory (session context preservation)')
|
|
68
51
|
.option('--no-memory', 'Disable working memory hooks')
|
|
52
|
+
.option('--hud', 'Enable HUD (git info, context usage, session stats)')
|
|
53
|
+
.option('--no-hud', 'Disable HUD status line')
|
|
54
|
+
.option('--hud-only', 'Install only the HUD (no plugins, hooks, or extras)')
|
|
69
55
|
.action(async (options) => {
|
|
70
56
|
// Get package version
|
|
71
57
|
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
|
@@ -82,7 +68,11 @@ export const initCommand = new Command('init')
|
|
|
82
68
|
p.intro(color.bgCyan(color.black(` DevFlow v${version} `)));
|
|
83
69
|
// Determine installation scope
|
|
84
70
|
let scope = 'user';
|
|
85
|
-
if (options.
|
|
71
|
+
if (options.hudOnly) {
|
|
72
|
+
// --hud-only: skip scope prompt, always user scope
|
|
73
|
+
scope = 'user';
|
|
74
|
+
}
|
|
75
|
+
else if (options.scope) {
|
|
86
76
|
const normalizedScope = options.scope.toLowerCase();
|
|
87
77
|
if (normalizedScope !== 'user' && normalizedScope !== 'local') {
|
|
88
78
|
p.log.error('Invalid scope. Use "user" or "local"');
|
|
@@ -108,6 +98,70 @@ export const initCommand = new Command('init')
|
|
|
108
98
|
}
|
|
109
99
|
scope = selected;
|
|
110
100
|
}
|
|
101
|
+
// --hud-only: install only HUD (skip plugins, hooks, extras)
|
|
102
|
+
if (options.hudOnly) {
|
|
103
|
+
// Resolve paths
|
|
104
|
+
const paths = await getInstallationPaths(scope);
|
|
105
|
+
const claudeDir = paths.claudeDir;
|
|
106
|
+
const devflowDir = paths.devflowDir;
|
|
107
|
+
// Save HUD config
|
|
108
|
+
const existingHud = loadHudConfig();
|
|
109
|
+
saveHudConfig({ enabled: true, detail: existingHud.detail });
|
|
110
|
+
// Update statusLine in settings.json
|
|
111
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
112
|
+
try {
|
|
113
|
+
let content;
|
|
114
|
+
try {
|
|
115
|
+
content = await fs.readFile(settingsPath, 'utf-8');
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
content = '{}';
|
|
119
|
+
}
|
|
120
|
+
const updated = addHudStatusLine(content, devflowDir);
|
|
121
|
+
await fs.writeFile(settingsPath, updated, 'utf-8');
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
p.log.error(`Failed to update settings: ${error instanceof Error ? error.message : error}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
// Copy HUD scripts to devflow dir
|
|
128
|
+
const rootDir = path.resolve(__dirname, '..', '..');
|
|
129
|
+
const scriptsSource = path.join(rootDir, 'scripts');
|
|
130
|
+
const scriptsTarget = path.join(devflowDir, 'scripts');
|
|
131
|
+
try {
|
|
132
|
+
await fs.mkdir(scriptsTarget, { recursive: true });
|
|
133
|
+
// Copy hud.sh
|
|
134
|
+
await fs.copyFile(path.join(scriptsSource, 'hud.sh'), path.join(scriptsTarget, 'hud.sh'));
|
|
135
|
+
// Copy hud/ directory
|
|
136
|
+
const hudSource = path.join(scriptsSource, 'hud');
|
|
137
|
+
const hudTarget = path.join(scriptsTarget, 'hud');
|
|
138
|
+
await copyDirectory(hudSource, hudTarget);
|
|
139
|
+
if (process.platform !== 'win32') {
|
|
140
|
+
await fs.chmod(path.join(scriptsTarget, 'hud.sh'), 0o755);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
p.log.error(`Failed to copy HUD scripts: ${error instanceof Error ? error.message : error}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
// Write minimal manifest
|
|
148
|
+
const now = new Date().toISOString();
|
|
149
|
+
try {
|
|
150
|
+
await writeManifest(devflowDir, {
|
|
151
|
+
version,
|
|
152
|
+
plugins: [],
|
|
153
|
+
scope,
|
|
154
|
+
features: { teams: false, ambient: false, memory: false, hud: true },
|
|
155
|
+
installedAt: now,
|
|
156
|
+
updatedAt: now,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch { /* non-fatal */ }
|
|
160
|
+
p.log.success('HUD installed');
|
|
161
|
+
p.log.info(`Configure later: ${color.cyan('devflow hud --status')}`);
|
|
162
|
+
p.outro(color.green('HUD-only install complete.'));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
111
165
|
// Select plugins to install
|
|
112
166
|
let selectedPlugins = [];
|
|
113
167
|
if (options.plugin) {
|
|
@@ -120,12 +174,29 @@ export const initCommand = new Command('init')
|
|
|
120
174
|
}
|
|
121
175
|
}
|
|
122
176
|
else if (process.stdin.isTTY) {
|
|
177
|
+
// Short hints to prevent overflow in multiselect — full descriptions live in plugins.ts
|
|
178
|
+
const pluginHints = {
|
|
179
|
+
'devflow-specify': 'feature specs → GitHub issues',
|
|
180
|
+
'devflow-implement': 'explore, plan, code, review',
|
|
181
|
+
'devflow-code-review': 'parallel specialized reviewers',
|
|
182
|
+
'devflow-resolve': 'fix review issues by risk',
|
|
183
|
+
'devflow-debug': 'competing hypotheses',
|
|
184
|
+
'devflow-self-review': 'Simplifier + Scrutinizer',
|
|
185
|
+
'devflow-typescript': 'TypeScript patterns',
|
|
186
|
+
'devflow-react': 'React patterns',
|
|
187
|
+
'devflow-accessibility': 'WCAG compliance',
|
|
188
|
+
'devflow-frontend-design': 'typography, color, spacing',
|
|
189
|
+
'devflow-go': 'Go patterns',
|
|
190
|
+
'devflow-java': 'Java patterns',
|
|
191
|
+
'devflow-python': 'Python patterns',
|
|
192
|
+
'devflow-rust': 'Rust patterns',
|
|
193
|
+
};
|
|
123
194
|
const choices = DEVFLOW_PLUGINS
|
|
124
195
|
.filter(pl => pl.name !== 'devflow-core-skills' && pl.name !== 'devflow-ambient' && pl.name !== 'devflow-audit-claude')
|
|
125
196
|
.map(pl => ({
|
|
126
197
|
value: pl.name,
|
|
127
198
|
label: pl.name.replace('devflow-', ''),
|
|
128
|
-
hint: pl.description,
|
|
199
|
+
hint: pluginHints[pl.name] ?? pl.description,
|
|
129
200
|
}));
|
|
130
201
|
const preSelected = DEVFLOW_PLUGINS
|
|
131
202
|
.filter(pl => !pl.optional && pl.name !== 'devflow-core-skills' && pl.name !== 'devflow-ambient')
|
|
@@ -151,11 +222,14 @@ export const initCommand = new Command('init')
|
|
|
151
222
|
teamsEnabled = false;
|
|
152
223
|
}
|
|
153
224
|
else {
|
|
225
|
+
p.note('Agent Teams enable peer debate between agents — adversarial\n' +
|
|
226
|
+
'review, competing hypotheses in debugging, and consensus-driven\n' +
|
|
227
|
+
'exploration. Experimental: may be unstable.', 'Agent Teams');
|
|
154
228
|
const teamsChoice = await p.select({
|
|
155
229
|
message: 'Enable Agent Teams?',
|
|
156
230
|
options: [
|
|
157
|
-
{ value: false, label: '
|
|
158
|
-
{ value: true, label: 'Yes', hint: '
|
|
231
|
+
{ value: false, label: 'Not yet', hint: 'Recommended' },
|
|
232
|
+
{ value: true, label: 'Yes', hint: 'Experimental' },
|
|
159
233
|
],
|
|
160
234
|
});
|
|
161
235
|
if (p.isCancel(teamsChoice)) {
|
|
@@ -173,11 +247,15 @@ export const initCommand = new Command('init')
|
|
|
173
247
|
ambientEnabled = true;
|
|
174
248
|
}
|
|
175
249
|
else {
|
|
250
|
+
p.note('Auto-classifies every prompt by intent and depth. Loads relevant\n' +
|
|
251
|
+
'skills automatically and escalates to full agent pipelines\n' +
|
|
252
|
+
'(review, debug, implement) when the task warrants it.\n\n' +
|
|
253
|
+
'Adds a small amount of context to each prompt for classification.', 'Ambient Mode');
|
|
176
254
|
const ambientChoice = await p.select({
|
|
177
255
|
message: 'Enable ambient mode?',
|
|
178
256
|
options: [
|
|
179
|
-
{ value: true, label: 'Yes
|
|
180
|
-
{ value: false, label: 'No', hint: '
|
|
257
|
+
{ value: true, label: 'Yes', hint: 'Recommended' },
|
|
258
|
+
{ value: false, label: 'No', hint: 'Manual skill loading via slash commands' },
|
|
181
259
|
],
|
|
182
260
|
});
|
|
183
261
|
if (p.isCancel(ambientChoice)) {
|
|
@@ -195,8 +273,13 @@ export const initCommand = new Command('init')
|
|
|
195
273
|
memoryEnabled = true;
|
|
196
274
|
}
|
|
197
275
|
else {
|
|
276
|
+
p.note('Preserves session context across /clear, restarts, and context\n' +
|
|
277
|
+
'compaction. Clear your session at any point and resume right\n' +
|
|
278
|
+
'where you left off.\n\n' +
|
|
279
|
+
'Runs a background agent on session stop that consumes additional\n' +
|
|
280
|
+
'tokens. Consider skipping if token usage is a concern.', 'Working Memory');
|
|
198
281
|
const memoryChoice = await p.confirm({
|
|
199
|
-
message: 'Enable working memory? (
|
|
282
|
+
message: 'Enable working memory? (Recommended)',
|
|
200
283
|
initialValue: true,
|
|
201
284
|
});
|
|
202
285
|
if (p.isCancel(memoryChoice)) {
|
|
@@ -205,14 +288,131 @@ export const initCommand = new Command('init')
|
|
|
205
288
|
}
|
|
206
289
|
memoryEnabled = memoryChoice;
|
|
207
290
|
}
|
|
208
|
-
//
|
|
291
|
+
// HUD selection (yes/no)
|
|
292
|
+
let hudEnabled;
|
|
293
|
+
if (options.hud !== undefined) {
|
|
294
|
+
hudEnabled = options.hud;
|
|
295
|
+
}
|
|
296
|
+
else if (!process.stdin.isTTY) {
|
|
297
|
+
hudEnabled = true;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
p.note('The HUD displays git branch, context usage, and session stats\n' +
|
|
301
|
+
'in the Claude Code status bar. Configurable via devflow hud.', 'HUD');
|
|
302
|
+
const hudChoice = await p.confirm({
|
|
303
|
+
message: 'Enable HUD? (Recommended)',
|
|
304
|
+
initialValue: true,
|
|
305
|
+
});
|
|
306
|
+
if (p.isCancel(hudChoice)) {
|
|
307
|
+
p.cancel('Installation cancelled.');
|
|
308
|
+
process.exit(0);
|
|
309
|
+
}
|
|
310
|
+
hudEnabled = hudChoice;
|
|
311
|
+
}
|
|
312
|
+
// .claudeignore prompt (needs early git detection)
|
|
313
|
+
const earlyGitRoot = await getGitRoot();
|
|
314
|
+
let claudeignoreEnabled = true;
|
|
315
|
+
let discoveredProjects = [];
|
|
316
|
+
if (earlyGitRoot && process.stdin.isTTY) {
|
|
317
|
+
if (scope === 'user') {
|
|
318
|
+
// User scope: discover all projects and offer batch install
|
|
319
|
+
discoveredProjects = await discoverProjectGitRoots();
|
|
320
|
+
p.note('Scans all projects Claude has worked on and creates a\n' +
|
|
321
|
+
'.claudeignore in each git repository. Excludes secrets,\n' +
|
|
322
|
+
'API keys, dependencies, and build artifacts from context.', '.claudeignore');
|
|
323
|
+
if (discoveredProjects.length > 0) {
|
|
324
|
+
const maxShow = 5;
|
|
325
|
+
const projectLines = discoveredProjects.slice(0, maxShow).join('\n');
|
|
326
|
+
const overflow = discoveredProjects.length > maxShow
|
|
327
|
+
? `\n... (${discoveredProjects.length - maxShow} more)`
|
|
328
|
+
: '';
|
|
329
|
+
p.note(projectLines + overflow, `Discovered ${discoveredProjects.length} projects`);
|
|
330
|
+
const claudeignoreChoice = await p.confirm({
|
|
331
|
+
message: `Install .claudeignore to ${discoveredProjects.length} projects? (Recommended)`,
|
|
332
|
+
initialValue: true,
|
|
333
|
+
});
|
|
334
|
+
if (p.isCancel(claudeignoreChoice)) {
|
|
335
|
+
p.cancel('Installation cancelled.');
|
|
336
|
+
process.exit(0);
|
|
337
|
+
}
|
|
338
|
+
claudeignoreEnabled = claudeignoreChoice;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
// No projects discovered — fall back to current project only
|
|
342
|
+
const claudeignoreChoice = await p.confirm({
|
|
343
|
+
message: 'Create .claudeignore? (Recommended)',
|
|
344
|
+
initialValue: true,
|
|
345
|
+
});
|
|
346
|
+
if (p.isCancel(claudeignoreChoice)) {
|
|
347
|
+
p.cancel('Installation cancelled.');
|
|
348
|
+
process.exit(0);
|
|
349
|
+
}
|
|
350
|
+
claudeignoreEnabled = claudeignoreChoice;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
// Local scope: single-project note
|
|
355
|
+
p.note('Creates a .claudeignore in this project that excludes\n' +
|
|
356
|
+
'secrets, API keys, dependencies, and build artifacts from\n' +
|
|
357
|
+
'Claude\'s context window.', '.claudeignore');
|
|
358
|
+
const claudeignoreChoice = await p.confirm({
|
|
359
|
+
message: 'Create .claudeignore? (Recommended)',
|
|
360
|
+
initialValue: true,
|
|
361
|
+
});
|
|
362
|
+
if (p.isCancel(claudeignoreChoice)) {
|
|
363
|
+
p.cancel('Installation cancelled.');
|
|
364
|
+
process.exit(0);
|
|
365
|
+
}
|
|
366
|
+
claudeignoreEnabled = claudeignoreChoice;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else if (!earlyGitRoot) {
|
|
370
|
+
claudeignoreEnabled = false;
|
|
371
|
+
}
|
|
372
|
+
// Safe-delete detection + prompt (all detection early, action deferred)
|
|
373
|
+
const platform = detectPlatform();
|
|
374
|
+
const shell = detectShell();
|
|
375
|
+
const safeDeleteInfo = getSafeDeleteInfo(platform);
|
|
376
|
+
const safeDeleteAvailable = hasSafeDelete(platform);
|
|
377
|
+
const profilePath = getProfilePath(shell);
|
|
378
|
+
let safeDeleteAction = 'skip';
|
|
379
|
+
let safeDeleteBlock = null;
|
|
380
|
+
if (process.stdin.isTTY && profilePath && safeDeleteAvailable) {
|
|
381
|
+
const trashCmd = safeDeleteInfo.command;
|
|
382
|
+
safeDeleteBlock = generateSafeDeleteBlock(shell, process.platform, trashCmd);
|
|
383
|
+
if (safeDeleteBlock) {
|
|
384
|
+
const installedVersion = await getInstalledVersion(profilePath);
|
|
385
|
+
if (installedVersion === SAFE_DELETE_BLOCK_VERSION) {
|
|
386
|
+
safeDeleteAction = 'skip'; // already current
|
|
387
|
+
}
|
|
388
|
+
else if (installedVersion > 0) {
|
|
389
|
+
safeDeleteAction = 'upgrade'; // auto-upgrade, no prompt needed
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
// Fresh install — prompt
|
|
393
|
+
p.note('Overrides rm to use your system trash CLI instead of permanent\n' +
|
|
394
|
+
'deletion. Prevents accidental data loss from rm -rf.', 'Safe Delete');
|
|
395
|
+
const safeDeleteConfirm = await p.confirm({
|
|
396
|
+
message: `Install safe-delete to ${profilePath}? (uses ${trashCmd ?? 'recycle bin'})`,
|
|
397
|
+
initialValue: true,
|
|
398
|
+
});
|
|
399
|
+
if (!p.isCancel(safeDeleteConfirm) && safeDeleteConfirm) {
|
|
400
|
+
safeDeleteAction = 'install';
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Security deny list placement (user scope + TTY only — last prompt before install)
|
|
209
406
|
let securityMode = 'user';
|
|
210
407
|
if (scope === 'user' && process.stdin.isTTY) {
|
|
408
|
+
p.note('DevFlow includes a security deny list that blocks dangerous\n' +
|
|
409
|
+
'commands (rm -rf, sudo, eval, etc). It can be installed as a\n' +
|
|
410
|
+
'read-only system file or in your editable settings.json.', 'Security Deny List');
|
|
211
411
|
const securityChoice = await p.select({
|
|
212
|
-
message: 'How should DevFlow install the
|
|
412
|
+
message: 'How should DevFlow install the deny list?',
|
|
213
413
|
options: [
|
|
214
|
-
{ value: 'managed', label: 'Managed settings
|
|
215
|
-
{ value: 'user', label: 'User settings', hint: '
|
|
414
|
+
{ value: 'managed', label: 'Managed settings', hint: 'Recommended — read-only, cannot be overridden' },
|
|
415
|
+
{ value: 'user', label: 'User settings', hint: 'Editable in settings.json' },
|
|
216
416
|
],
|
|
217
417
|
});
|
|
218
418
|
if (p.isCancel(securityChoice)) {
|
|
@@ -221,7 +421,32 @@ export const initCommand = new Command('init')
|
|
|
221
421
|
}
|
|
222
422
|
securityMode = securityChoice;
|
|
223
423
|
}
|
|
224
|
-
//
|
|
424
|
+
// Managed settings sudo confirmation (last interactive step — may prompt for password)
|
|
425
|
+
let managedSettingsConfirmed = false;
|
|
426
|
+
if (securityMode === 'managed') {
|
|
427
|
+
p.note('This writes a read-only security deny list to a system directory\n' +
|
|
428
|
+
'and may prompt for your password (sudo).\n\n' +
|
|
429
|
+
'Not sure about this? Paste this into another Claude Code session:\n\n' +
|
|
430
|
+
' "I\'m installing DevFlow and it wants to write a\n' +
|
|
431
|
+
' managed-settings.json file using sudo. Review the source\n' +
|
|
432
|
+
' at https://github.com/dean0x/devflow and tell me if\n' +
|
|
433
|
+
' it\'s safe."', 'Managed Settings');
|
|
434
|
+
const sudoChoice = await p.select({
|
|
435
|
+
message: 'Continue with managed settings?',
|
|
436
|
+
options: [
|
|
437
|
+
{ value: 'yes', label: 'Yes, continue', hint: 'May prompt for your password' },
|
|
438
|
+
{ value: 'no', label: 'No, fall back to settings.json', hint: 'Editable user settings instead' },
|
|
439
|
+
],
|
|
440
|
+
});
|
|
441
|
+
if (p.isCancel(sudoChoice)) {
|
|
442
|
+
p.cancel('Installation cancelled.');
|
|
443
|
+
process.exit(0);
|
|
444
|
+
}
|
|
445
|
+
managedSettingsConfirmed = sudoChoice === 'yes';
|
|
446
|
+
}
|
|
447
|
+
// ╭──────────────────────────────────────────────────────────╮
|
|
448
|
+
// │ All prompts collected — installation begins │
|
|
449
|
+
// ╰──────────────────────────────────────────────────────────╯
|
|
225
450
|
const s = p.spinner();
|
|
226
451
|
s.start('Resolving paths');
|
|
227
452
|
// Get installation paths
|
|
@@ -232,7 +457,7 @@ export const initCommand = new Command('init')
|
|
|
232
457
|
const paths = await getInstallationPaths(scope);
|
|
233
458
|
claudeDir = paths.claudeDir;
|
|
234
459
|
devflowDir = paths.devflowDir;
|
|
235
|
-
gitRoot = paths.gitRoot ??
|
|
460
|
+
gitRoot = paths.gitRoot ?? earlyGitRoot;
|
|
236
461
|
}
|
|
237
462
|
catch (error) {
|
|
238
463
|
s.stop('Path resolution failed');
|
|
@@ -316,8 +541,8 @@ export const initCommand = new Command('init')
|
|
|
316
541
|
process.exit(1);
|
|
317
542
|
}
|
|
318
543
|
}
|
|
319
|
-
s.stop('Plugins installed');
|
|
320
544
|
// Clean up stale skills from previous installations
|
|
545
|
+
s.message('Cleaning up');
|
|
321
546
|
const skillsDir = path.join(claudeDir, 'skills');
|
|
322
547
|
let staleRemoved = 0;
|
|
323
548
|
for (const legacy of LEGACY_SKILL_NAMES) {
|
|
@@ -351,114 +576,90 @@ export const initCommand = new Command('init')
|
|
|
351
576
|
if (staleCommandsRemoved > 0 && verbose) {
|
|
352
577
|
p.log.info(`Cleaned up ${staleCommandsRemoved} legacy command(s)`);
|
|
353
578
|
}
|
|
354
|
-
// ===
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
options: extrasOptions,
|
|
361
|
-
initialValues: extrasOptions.map(o => o.value),
|
|
362
|
-
required: false,
|
|
363
|
-
});
|
|
364
|
-
if (p.isCancel(extrasSelection)) {
|
|
365
|
-
p.cancel('Installation cancelled.');
|
|
366
|
-
process.exit(0);
|
|
367
|
-
}
|
|
368
|
-
selectedExtras = extrasSelection;
|
|
579
|
+
// === Settings & hooks (all automatic based on collected choices) ===
|
|
580
|
+
s.message('Configuring settings');
|
|
581
|
+
// Determine effective security mode (managed settings executed later, after safe-delete)
|
|
582
|
+
let effectiveSecurityMode = securityMode;
|
|
583
|
+
if (securityMode === 'managed' && !managedSettingsConfirmed) {
|
|
584
|
+
effectiveSecurityMode = 'user';
|
|
369
585
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (securityMode === 'managed') {
|
|
378
|
-
p.note('This writes a read-only security deny list to a system directory\n' +
|
|
379
|
-
'and may prompt for your password (sudo).\n\n' +
|
|
380
|
-
'Not sure about this? Paste this into another Claude Code session:\n\n' +
|
|
381
|
-
' "I\'m installing DevFlow and it wants to write a\n' +
|
|
382
|
-
' managed-settings.json file using sudo. Review the source\n' +
|
|
383
|
-
' at https://github.com/dean0x/devflow and tell me if\n' +
|
|
384
|
-
' it\'s safe."', 'Managed Settings');
|
|
385
|
-
const sudoChoice = await p.select({
|
|
386
|
-
message: 'Continue with managed settings?',
|
|
387
|
-
options: [
|
|
388
|
-
{ value: 'yes', label: 'Yes, continue', hint: 'May prompt for your password' },
|
|
389
|
-
{ value: 'no', label: 'No, fall back to settings.json', hint: 'Deny list stored in editable user settings instead' },
|
|
390
|
-
],
|
|
391
|
-
});
|
|
392
|
-
if (p.isCancel(sudoChoice)) {
|
|
393
|
-
p.cancel('Installation cancelled.');
|
|
394
|
-
process.exit(0);
|
|
395
|
-
}
|
|
396
|
-
if (sudoChoice === 'yes') {
|
|
397
|
-
const managed = await installManagedSettings(rootDir, verbose);
|
|
398
|
-
if (!managed) {
|
|
399
|
-
p.log.warn('Managed settings write failed — falling back to user settings');
|
|
400
|
-
effectiveSecurityMode = 'user';
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
effectiveSecurityMode = 'user';
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
await installSettings(claudeDir, rootDir, devflowDir, verbose, teamsEnabled, effectiveSecurityMode);
|
|
408
|
-
// Install ambient hook if enabled
|
|
586
|
+
await installSettings(claudeDir, rootDir, devflowDir, verbose, teamsEnabled, effectiveSecurityMode);
|
|
587
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
588
|
+
// Configure ambient hook, memory hooks, and HUD statusLine in a single read-modify-write pass
|
|
589
|
+
try {
|
|
590
|
+
let content = await fs.readFile(settingsPath, 'utf-8');
|
|
591
|
+
const original = content;
|
|
592
|
+
// Ambient hook
|
|
409
593
|
if (ambientEnabled) {
|
|
410
|
-
|
|
411
|
-
try {
|
|
412
|
-
const content = await fs.readFile(settingsPath, 'utf-8');
|
|
413
|
-
const updated = addAmbientHook(content, devflowDir);
|
|
414
|
-
if (updated !== content) {
|
|
415
|
-
await fs.writeFile(settingsPath, updated, 'utf-8');
|
|
416
|
-
if (verbose) {
|
|
417
|
-
p.log.success('Ambient mode hook installed');
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
catch { /* settings.json may not exist yet */ }
|
|
594
|
+
content = addAmbientHook(content, devflowDir);
|
|
422
595
|
}
|
|
423
|
-
//
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
596
|
+
// Memory hooks — always remove-then-add to upgrade hook format (e.g., .sh → run-hook)
|
|
597
|
+
const cleaned = removeMemoryHooks(content);
|
|
598
|
+
content = memoryEnabled ? addMemoryHooks(cleaned, devflowDir) : cleaned;
|
|
599
|
+
// HUD statusLine
|
|
600
|
+
content = hudEnabled
|
|
601
|
+
? addHudStatusLine(content, devflowDir)
|
|
602
|
+
: removeHudStatusLine(content);
|
|
603
|
+
if (content !== original) {
|
|
604
|
+
await fs.writeFile(settingsPath, content, 'utf-8');
|
|
605
|
+
if (verbose) {
|
|
606
|
+
if (ambientEnabled)
|
|
607
|
+
p.log.success('Ambient mode hook installed');
|
|
608
|
+
p.log.info(`Working memory ${memoryEnabled ? 'enabled' : 'disabled'}`);
|
|
609
|
+
p.log.info(`HUD ${hudEnabled ? 'enabled' : 'disabled'}`);
|
|
437
610
|
}
|
|
438
611
|
}
|
|
439
|
-
catch { /* settings.json may not exist yet */ }
|
|
440
|
-
// Ensure .memory/ exists when memory is enabled (hooks are no-ops without it)
|
|
441
|
-
if (memoryEnabled) {
|
|
442
|
-
await createMemoryDir(verbose);
|
|
443
|
-
await migrateMemoryFiles(verbose);
|
|
444
|
-
}
|
|
445
612
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
613
|
+
catch { /* settings.json may not exist yet */ }
|
|
614
|
+
// Ensure .memory/ exists when memory is enabled (hooks are no-ops without it)
|
|
615
|
+
if (memoryEnabled) {
|
|
616
|
+
await createMemoryDir(verbose);
|
|
617
|
+
await migrateMemoryFiles(verbose);
|
|
618
|
+
}
|
|
619
|
+
// Configure HUD
|
|
620
|
+
const existingHud = loadHudConfig();
|
|
621
|
+
saveHudConfig({ enabled: hudEnabled, detail: existingHud.detail });
|
|
622
|
+
// File extras
|
|
623
|
+
if (claudeignoreEnabled) {
|
|
624
|
+
if (scope === 'user' && discoveredProjects.length > 0) {
|
|
625
|
+
const results = await Promise.all(discoveredProjects.map(root => installClaudeignore(root, rootDir, verbose)));
|
|
626
|
+
const created = results.filter(Boolean).length;
|
|
627
|
+
if (created > 0) {
|
|
628
|
+
p.log.success(`.claudeignore created in ${created} project(s)`);
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
p.log.info(`.claudeignore already exists in all ${discoveredProjects.length} project(s)`);
|
|
632
|
+
}
|
|
452
633
|
}
|
|
453
|
-
if (
|
|
454
|
-
await
|
|
634
|
+
else if (gitRoot) {
|
|
635
|
+
await installClaudeignore(gitRoot, rootDir, verbose);
|
|
455
636
|
}
|
|
456
|
-
|
|
457
|
-
|
|
637
|
+
}
|
|
638
|
+
if (scope === 'local' && gitRoot) {
|
|
639
|
+
await updateGitignore(gitRoot, verbose);
|
|
640
|
+
}
|
|
641
|
+
if (scope === 'local') {
|
|
642
|
+
await createDocsStructure(verbose);
|
|
643
|
+
}
|
|
644
|
+
// Safe-delete execution (decision was captured during prompt phase)
|
|
645
|
+
if (safeDeleteAction === 'install' && safeDeleteBlock && profilePath) {
|
|
646
|
+
await installToProfile(profilePath, safeDeleteBlock);
|
|
647
|
+
}
|
|
648
|
+
else if (safeDeleteAction === 'upgrade' && safeDeleteBlock && profilePath) {
|
|
649
|
+
await removeFromProfile(profilePath);
|
|
650
|
+
await installToProfile(profilePath, safeDeleteBlock);
|
|
651
|
+
}
|
|
652
|
+
// Managed settings (last install step — sudo may prompt for password)
|
|
653
|
+
if (securityMode === 'managed' && managedSettingsConfirmed) {
|
|
654
|
+
s.stop('Installing managed settings (may prompt for password)');
|
|
655
|
+
const managed = await installManagedSettings(rootDir, verbose);
|
|
656
|
+
if (!managed) {
|
|
657
|
+
p.log.warn('Managed settings write failed — falling back to user settings');
|
|
458
658
|
}
|
|
459
|
-
|
|
659
|
+
s.start('Finalizing');
|
|
460
660
|
}
|
|
461
|
-
|
|
661
|
+
s.stop('Installation complete');
|
|
662
|
+
// === Summary ===
|
|
462
663
|
if (usedNativeCli) {
|
|
463
664
|
p.log.success('Installed via Claude plugin system');
|
|
464
665
|
}
|
|
@@ -472,54 +673,34 @@ export const initCommand = new Command('init')
|
|
|
472
673
|
.join(' ');
|
|
473
674
|
p.note(commandsNote, 'Available commands');
|
|
474
675
|
}
|
|
475
|
-
// Safe-delete
|
|
476
|
-
if (
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const safeDeleteAvailable = hasSafeDelete(platform);
|
|
481
|
-
const profilePath = getProfilePath(shell);
|
|
482
|
-
if (process.stdin.isTTY && profilePath) {
|
|
483
|
-
if (!safeDeleteAvailable && safeDeleteInfo.installHint) {
|
|
484
|
-
p.log.info(`Install ${color.cyan(safeDeleteInfo.command ?? 'trash')} first: ${color.dim(safeDeleteInfo.installHint)}`);
|
|
485
|
-
p.log.info(`Then re-run ${color.cyan('devflow init')} to auto-configure safe-delete.`);
|
|
486
|
-
}
|
|
487
|
-
else if (safeDeleteAvailable) {
|
|
488
|
-
const trashCmd = safeDeleteInfo.command;
|
|
489
|
-
const block = generateSafeDeleteBlock(shell, process.platform, trashCmd);
|
|
490
|
-
if (block) {
|
|
491
|
-
const installedVersion = await getInstalledVersion(profilePath);
|
|
492
|
-
if (installedVersion === SAFE_DELETE_BLOCK_VERSION) {
|
|
493
|
-
p.log.info(`Safe-delete already configured in ${color.dim(profilePath)}`);
|
|
494
|
-
}
|
|
495
|
-
else if (installedVersion > 0) {
|
|
496
|
-
await removeFromProfile(profilePath);
|
|
497
|
-
await installToProfile(profilePath, block);
|
|
498
|
-
p.log.success(`Safe-delete upgraded in ${color.dim(profilePath)}`);
|
|
499
|
-
p.log.info('Restart your shell or run: ' + color.cyan(`source ${profilePath}`));
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
const confirm = await p.confirm({
|
|
503
|
-
message: `Install safe-delete to ${profilePath}? (overrides rm to use ${trashCmd ?? 'recycle bin'})`,
|
|
504
|
-
initialValue: true,
|
|
505
|
-
});
|
|
506
|
-
if (!p.isCancel(confirm) && confirm) {
|
|
507
|
-
await installToProfile(profilePath, block);
|
|
508
|
-
p.log.success(`Safe-delete installed to ${color.dim(profilePath)}`);
|
|
509
|
-
p.log.info('Restart your shell or run: ' + color.cyan(`source ${profilePath}`));
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
676
|
+
// Safe-delete status messages (after spinner)
|
|
677
|
+
if (process.stdin.isTTY && profilePath) {
|
|
678
|
+
if (safeDeleteAction === 'install') {
|
|
679
|
+
p.log.success(`Safe-delete installed to ${color.dim(profilePath)}`);
|
|
680
|
+
p.log.info('Restart your shell or run: ' + color.cyan(`source ${profilePath}`));
|
|
514
681
|
}
|
|
515
|
-
else if (
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
682
|
+
else if (safeDeleteAction === 'upgrade') {
|
|
683
|
+
p.log.success(`Safe-delete upgraded in ${color.dim(profilePath)}`);
|
|
684
|
+
p.log.info('Restart your shell or run: ' + color.cyan(`source ${profilePath}`));
|
|
685
|
+
}
|
|
686
|
+
else if (safeDeleteAvailable && safeDeleteBlock) {
|
|
687
|
+
const installedVersion = await getInstalledVersion(profilePath);
|
|
688
|
+
if (installedVersion === SAFE_DELETE_BLOCK_VERSION) {
|
|
689
|
+
p.log.info(`Safe-delete already configured in ${color.dim(profilePath)}`);
|
|
521
690
|
}
|
|
522
691
|
}
|
|
692
|
+
else if (!safeDeleteAvailable && safeDeleteInfo.installHint) {
|
|
693
|
+
p.log.info(`Install ${color.cyan(safeDeleteInfo.command ?? 'trash')} first: ${color.dim(safeDeleteInfo.installHint)}`);
|
|
694
|
+
p.log.info(`Then re-run ${color.cyan('devflow init')} to auto-configure safe-delete.`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
else if (!process.stdin.isTTY) {
|
|
698
|
+
if (safeDeleteAvailable && safeDeleteInfo.command) {
|
|
699
|
+
p.log.info(`Safe-delete available (${safeDeleteInfo.command}). Run interactively to auto-install.`);
|
|
700
|
+
}
|
|
701
|
+
else if (safeDeleteInfo.installHint) {
|
|
702
|
+
p.log.info(`Protect against accidental ${color.red('rm -rf')}: ${color.cyan(safeDeleteInfo.installHint)}`);
|
|
703
|
+
}
|
|
523
704
|
}
|
|
524
705
|
// Verbose mode: show details
|
|
525
706
|
if (verbose) {
|
|
@@ -542,7 +723,7 @@ export const initCommand = new Command('init')
|
|
|
542
723
|
version,
|
|
543
724
|
plugins: resolvePluginList(installedPluginNames, existingManifest, !!options.plugin),
|
|
544
725
|
scope,
|
|
545
|
-
features: { teams: teamsEnabled, ambient: ambientEnabled, memory: memoryEnabled },
|
|
726
|
+
features: { teams: teamsEnabled, ambient: ambientEnabled, memory: memoryEnabled, hud: hudEnabled },
|
|
546
727
|
installedAt: existingManifest?.installedAt ?? now,
|
|
547
728
|
updatedAt: now,
|
|
548
729
|
};
|