@soleri/cli 9.14.3 → 9.16.7
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/dist/commands/add-domain.js +65 -0
- package/dist/commands/add-domain.js.map +1 -1
- package/dist/commands/agent.js +51 -20
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/brain.d.ts +8 -0
- package/dist/commands/brain.js +83 -0
- package/dist/commands/brain.js.map +1 -0
- package/dist/commands/chat.d.ts +11 -0
- package/dist/commands/chat.js +295 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/create.js +11 -9
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/dev.js +19 -0
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/dream.js +1 -12
- package/dist/commands/dream.js.map +1 -1
- package/dist/commands/hooks.js +29 -0
- package/dist/commands/hooks.js.map +1 -1
- package/dist/commands/install.js +3 -9
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/knowledge.d.ts +9 -0
- package/dist/commands/knowledge.js +99 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/pack.js +164 -3
- package/dist/commands/pack.js.map +1 -1
- package/dist/commands/schedule.d.ts +11 -0
- package/dist/commands/schedule.js +130 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/staging.d.ts +2 -17
- package/dist/commands/staging.js +4 -4
- package/dist/commands/staging.js.map +1 -1
- package/dist/commands/validate-skills.d.ts +10 -0
- package/dist/commands/validate-skills.js +47 -0
- package/dist/commands/validate-skills.js.map +1 -0
- package/dist/commands/vault.js +2 -11
- package/dist/commands/vault.js.map +1 -1
- package/dist/main.js +10 -0
- package/dist/main.js.map +1 -1
- package/dist/utils/checks.js +17 -32
- package/dist/utils/checks.js.map +1 -1
- package/dist/utils/core-resolver.d.ts +3 -0
- package/dist/utils/core-resolver.js +38 -0
- package/dist/utils/core-resolver.js.map +1 -0
- package/dist/utils/vault-db.d.ts +5 -0
- package/dist/utils/vault-db.js +17 -0
- package/dist/utils/vault-db.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/doctor.test.ts +46 -1
- package/src/__tests__/hook-packs.test.ts +0 -18
- package/src/__tests__/hooks-convert.test.ts +0 -28
- package/src/__tests__/hooks-sync.test.ts +109 -0
- package/src/__tests__/hooks.test.ts +0 -20
- package/src/__tests__/install-verify.test.ts +1 -1
- package/src/__tests__/install.test.ts +7 -10
- package/src/__tests__/update.test.ts +0 -19
- package/src/__tests__/validator.test.ts +0 -16
- package/src/commands/add-domain.ts +89 -1
- package/src/commands/agent.ts +53 -17
- package/src/commands/brain.ts +93 -0
- package/src/commands/chat.ts +373 -0
- package/src/commands/create.ts +11 -8
- package/src/commands/dev.ts +21 -0
- package/src/commands/dream.ts +1 -11
- package/src/commands/hooks.ts +32 -0
- package/src/commands/install.ts +3 -8
- package/src/commands/knowledge.ts +124 -0
- package/src/commands/pack.ts +219 -1
- package/src/commands/schedule.ts +150 -0
- package/src/commands/staging.ts +5 -5
- package/src/commands/validate-skills.ts +58 -0
- package/src/commands/vault.ts +2 -11
- package/src/main.ts +10 -0
- package/src/utils/checks.ts +18 -30
- package/src/utils/core-resolver.ts +39 -0
- package/src/utils/vault-db.ts +15 -0
- package/dist/hook-packs/converter/template.test.ts +0 -133
- package/dist/hook-packs/yolo-safety/scripts/anti-deletion.sh +0 -274
- package/dist/prompts/archetypes.d.ts +0 -22
- package/dist/prompts/archetypes.js +0 -298
- package/dist/prompts/archetypes.js.map +0 -1
- package/dist/prompts/playbook.d.ts +0 -64
- package/dist/prompts/playbook.js +0 -436
- package/dist/prompts/playbook.js.map +0 -1
- package/dist/utils/format-paths.d.ts +0 -14
- package/dist/utils/format-paths.js +0 -27
- package/dist/utils/format-paths.js.map +0 -1
- package/src/__tests__/dream.test.ts +0 -119
package/src/utils/checks.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { execFileSync } from 'node:child_process';
|
|
7
|
-
import { homedir } from 'node:os';
|
|
8
7
|
import { detectAgent, type AgentFormat } from './agent-context.js';
|
|
8
|
+
import { detectArtifacts } from './agent-artifacts.js';
|
|
9
|
+
import { resolveCorePackageJsonPath } from './core-resolver.js';
|
|
9
10
|
import { getInstalledPacks } from '../hook-packs/registry.js';
|
|
10
11
|
|
|
11
12
|
export interface CheckResult {
|
|
@@ -166,49 +167,36 @@ export function checkInstructionsDir(agentPath: string): CheckResult {
|
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
export function checkEngineReachable(): CheckResult {
|
|
169
|
-
|
|
170
|
-
require.resolve('@soleri/core/package.json');
|
|
170
|
+
if (resolveCorePackageJsonPath() !== null) {
|
|
171
171
|
return { status: 'pass', label: 'Engine', detail: '@soleri/core reachable' };
|
|
172
|
-
} catch {
|
|
173
|
-
return {
|
|
174
|
-
status: 'fail',
|
|
175
|
-
label: 'Engine',
|
|
176
|
-
detail: '@soleri/core not found — engine is required for file-tree agents',
|
|
177
|
-
};
|
|
178
172
|
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
status: 'fail',
|
|
176
|
+
label: 'Engine',
|
|
177
|
+
detail: '@soleri/core not found — engine is required for file-tree agents',
|
|
178
|
+
};
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
function checkMcpRegistration(dir?: string): CheckResult {
|
|
182
182
|
const ctx = detectAgent(dir);
|
|
183
183
|
if (!ctx) return { status: 'warn', label: 'MCP registration', detail: 'no agent detected' };
|
|
184
184
|
|
|
185
|
-
const
|
|
186
|
-
if (
|
|
185
|
+
const artifacts = detectArtifacts(ctx.agentId, ctx.agentPath);
|
|
186
|
+
if (artifacts.mcpServerEntries.length === 0) {
|
|
187
187
|
return {
|
|
188
188
|
status: 'warn',
|
|
189
189
|
label: 'MCP registration',
|
|
190
|
-
detail:
|
|
190
|
+
detail: `not found in ~/.claude.json, ~/.codex/config.toml, or ~/.config/opencode/opencode.json`,
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
label: 'MCP registration',
|
|
201
|
-
detail: `registered as "${ctx.agentId}"`,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
status: 'warn',
|
|
206
|
-
label: 'MCP registration',
|
|
207
|
-
detail: `"${ctx.agentId}" not found in ~/.claude.json`,
|
|
208
|
-
};
|
|
209
|
-
} catch {
|
|
210
|
-
return { status: 'fail', label: 'MCP registration', detail: 'failed to parse ~/.claude.json' };
|
|
211
|
-
}
|
|
194
|
+
const targets = [...new Set(artifacts.mcpServerEntries.map((entry) => entry.target))];
|
|
195
|
+
return {
|
|
196
|
+
status: 'pass',
|
|
197
|
+
label: 'MCP registration',
|
|
198
|
+
detail: `registered in ${targets.join(', ')}`,
|
|
199
|
+
};
|
|
212
200
|
}
|
|
213
201
|
|
|
214
202
|
function checkCognee(): CheckResult {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
function resolveCoreEntryPath(): string | null {
|
|
6
|
+
try {
|
|
7
|
+
return fileURLToPath(import.meta.resolve('@soleri/core'));
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveCorePackageJsonPath(): string | null {
|
|
14
|
+
const entryPath = resolveCoreEntryPath();
|
|
15
|
+
if (!entryPath) return null;
|
|
16
|
+
|
|
17
|
+
const packageJsonPath = join(dirname(entryPath), '..', 'package.json');
|
|
18
|
+
return existsSync(packageJsonPath) ? packageJsonPath : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function readInstalledCoreVersion(): string | null {
|
|
22
|
+
const packageJsonPath = resolveCorePackageJsonPath();
|
|
23
|
+
if (!packageJsonPath) return null;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as { version?: unknown };
|
|
27
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveInstalledEngineBin(): string | null {
|
|
34
|
+
const entryPath = resolveCoreEntryPath();
|
|
35
|
+
if (!entryPath) return null;
|
|
36
|
+
|
|
37
|
+
const engineBinPath = join(dirname(entryPath), 'engine', 'bin', 'soleri-engine.js');
|
|
38
|
+
return existsSync(engineBinPath) ? engineBinPath : null;
|
|
39
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { SOLERI_HOME } from '@soleri/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the vault DB path for a given agent.
|
|
7
|
+
* Checks the current path first, then falls back to the legacy dot-prefixed path.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveVaultDbPath(agentId: string): string | null {
|
|
10
|
+
const newDbPath = join(SOLERI_HOME, agentId, 'vault.db');
|
|
11
|
+
const legacyDbPath = join(SOLERI_HOME, '..', `.${agentId}`, 'vault.db');
|
|
12
|
+
if (existsSync(newDbPath)) return newDbPath;
|
|
13
|
+
if (existsSync(legacyDbPath)) return legacyDbPath;
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { generateHookScript, generateManifest, HOOK_EVENTS, ACTION_LEVELS } from './template.js';
|
|
3
|
-
import type { HookConversionConfig } from './template.js';
|
|
4
|
-
|
|
5
|
-
describe('generateHookScript', () => {
|
|
6
|
-
const baseConfig: HookConversionConfig = {
|
|
7
|
-
name: 'test-hook',
|
|
8
|
-
event: 'PreToolUse',
|
|
9
|
-
toolMatcher: 'Write|Edit',
|
|
10
|
-
filePatterns: ['**/marketing/**'],
|
|
11
|
-
action: 'remind',
|
|
12
|
-
message: 'Check brand guidelines before editing marketing files',
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
it('should generate a valid POSIX shell script', () => {
|
|
16
|
-
const script = generateHookScript(baseConfig);
|
|
17
|
-
expect(script).toContain('#!/bin/sh');
|
|
18
|
-
expect(script).toContain('set -eu');
|
|
19
|
-
expect(script).toContain('INPUT=$(cat)');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should include tool matcher for PreToolUse', () => {
|
|
23
|
-
const script = generateHookScript(baseConfig);
|
|
24
|
-
expect(script).toContain('TOOL_NAME=');
|
|
25
|
-
expect(script).toContain('Write|Edit');
|
|
26
|
-
expect(script).toContain('case "$TOOL_NAME" in');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should include file pattern matching', () => {
|
|
30
|
-
const script = generateHookScript(baseConfig);
|
|
31
|
-
expect(script).toContain('FILE_PATH=');
|
|
32
|
-
expect(script).toContain('MATCHED=false');
|
|
33
|
-
expect(script).toContain('marketing');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should output remind action by default', () => {
|
|
37
|
-
const script = generateHookScript(baseConfig);
|
|
38
|
-
expect(script).toContain('REMINDER:');
|
|
39
|
-
expect(script).toContain('continue: true');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should output warn action', () => {
|
|
43
|
-
const script = generateHookScript({ ...baseConfig, action: 'warn' });
|
|
44
|
-
expect(script).toContain('WARNING:');
|
|
45
|
-
expect(script).toContain('continue: true');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should output block action', () => {
|
|
49
|
-
const script = generateHookScript({ ...baseConfig, action: 'block' });
|
|
50
|
-
expect(script).toContain('BLOCKED:');
|
|
51
|
-
expect(script).toContain('continue: false');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should skip tool matcher for non-tool events', () => {
|
|
55
|
-
const script = generateHookScript({ ...baseConfig, event: 'PreCompact' });
|
|
56
|
-
expect(script).not.toContain('TOOL_NAME');
|
|
57
|
-
expect(script).not.toContain('case');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should skip file pattern matching when no patterns', () => {
|
|
61
|
-
const script = generateHookScript({ ...baseConfig, filePatterns: undefined });
|
|
62
|
-
expect(script).not.toContain('FILE_PATH');
|
|
63
|
-
expect(script).not.toContain('MATCHED');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should generate scripts for all 5 hook events', () => {
|
|
67
|
-
for (const event of HOOK_EVENTS) {
|
|
68
|
-
const script = generateHookScript({ ...baseConfig, event });
|
|
69
|
-
expect(script).toContain(`# Event: ${event}`);
|
|
70
|
-
expect(script).toContain('#!/bin/sh');
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should escape single quotes in messages', () => {
|
|
75
|
-
const script = generateHookScript({ ...baseConfig, message: "Don't forget the guidelines" });
|
|
76
|
-
// Should not have unbalanced quotes
|
|
77
|
-
expect(script).toContain('forget');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('generateManifest', () => {
|
|
82
|
-
const config: HookConversionConfig = {
|
|
83
|
-
name: 'my-hook',
|
|
84
|
-
event: 'PreToolUse',
|
|
85
|
-
toolMatcher: 'Write',
|
|
86
|
-
action: 'remind',
|
|
87
|
-
message: 'Test message',
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
it('should generate valid manifest with required fields', () => {
|
|
91
|
-
const manifest = generateManifest(config);
|
|
92
|
-
expect(manifest.name).toBe('my-hook');
|
|
93
|
-
expect(manifest.version).toBe('1.0.0');
|
|
94
|
-
expect(manifest.hooks).toEqual([]);
|
|
95
|
-
expect(manifest.scripts).toHaveLength(1);
|
|
96
|
-
expect(manifest.lifecycleHooks).toHaveLength(1);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should set script name and file correctly', () => {
|
|
100
|
-
const manifest = generateManifest(config);
|
|
101
|
-
expect(manifest.scripts![0].name).toBe('my-hook');
|
|
102
|
-
expect(manifest.scripts![0].file).toBe('my-hook.sh');
|
|
103
|
-
expect(manifest.scripts![0].targetDir).toBe('hooks');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should set lifecycle hook event and command', () => {
|
|
107
|
-
const manifest = generateManifest(config);
|
|
108
|
-
const lc = manifest.lifecycleHooks![0];
|
|
109
|
-
expect(lc.event).toBe('PreToolUse');
|
|
110
|
-
expect(lc.command).toBe('sh ~/.claude/hooks/my-hook.sh');
|
|
111
|
-
expect(lc.type).toBe('command');
|
|
112
|
-
expect(lc.timeout).toBe(10);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('should use description from config or fallback to message', () => {
|
|
116
|
-
expect(generateManifest(config).description).toBe('Test message');
|
|
117
|
-
expect(generateManifest({ ...config, description: 'Custom desc' }).description).toBe(
|
|
118
|
-
'Custom desc',
|
|
119
|
-
);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should include actionLevel', () => {
|
|
123
|
-
expect(generateManifest(config).actionLevel).toBe('remind');
|
|
124
|
-
expect(generateManifest({ ...config, action: 'block' }).actionLevel).toBe('block');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should generate manifests for all action levels', () => {
|
|
128
|
-
for (const action of ACTION_LEVELS) {
|
|
129
|
-
const manifest = generateManifest({ ...config, action });
|
|
130
|
-
expect(manifest.actionLevel).toBe(action);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
});
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# Anti-Deletion Staging Hook for Claude Code (Soleri Hook Pack: yolo-safety)
|
|
3
|
-
# PreToolUse -> Bash: intercepts destructive commands, stages files, blocks execution.
|
|
4
|
-
#
|
|
5
|
-
# Intercepted patterns:
|
|
6
|
-
# - rm / rmdir (files/dirs — stages first, then blocks)
|
|
7
|
-
# - git push --force (blocks outright)
|
|
8
|
-
# - git reset --hard (blocks outright)
|
|
9
|
-
# - git clean (blocks outright)
|
|
10
|
-
# - git checkout -- . (blocks outright)
|
|
11
|
-
# - git restore . (blocks outright)
|
|
12
|
-
# - mv ~/projects/... (blocks outright)
|
|
13
|
-
# - drop table (SQL — blocks outright)
|
|
14
|
-
# - docker rm / rmi (blocks outright)
|
|
15
|
-
#
|
|
16
|
-
# Catastrophic commands (rm -rf /, rm -rf ~) should stay in deny rules —
|
|
17
|
-
# this hook handles targeted deletes only.
|
|
18
|
-
#
|
|
19
|
-
# Dependencies: jq (required)
|
|
20
|
-
# POSIX sh compatible — no bash-specific features.
|
|
21
|
-
|
|
22
|
-
set -eu
|
|
23
|
-
|
|
24
|
-
STAGING_ROOT="$HOME/.soleri/staging"
|
|
25
|
-
INPUT=$(cat)
|
|
26
|
-
|
|
27
|
-
# Extract the command from stdin JSON
|
|
28
|
-
CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
29
|
-
|
|
30
|
-
# No command found — let it through
|
|
31
|
-
if [ -z "$CMD" ]; then
|
|
32
|
-
exit 0
|
|
33
|
-
fi
|
|
34
|
-
|
|
35
|
-
# --- Strip heredocs and quoted strings to avoid false positives ---
|
|
36
|
-
# Commands like: gh issue comment --body "$(cat <<'EOF' ... rmdir ... EOF)"
|
|
37
|
-
# contain destructive keywords in text, not as actual commands.
|
|
38
|
-
|
|
39
|
-
# Remove heredoc blocks (best-effort with sed)
|
|
40
|
-
STRIPPED=$(printf '%s' "$CMD" | sed -e "s/<<'[A-Za-z_]*'.*//g" -e 's/<<[A-Za-z_]*.*//g' 2>/dev/null || printf '%s' "$CMD")
|
|
41
|
-
# Remove double-quoted strings
|
|
42
|
-
STRIPPED=$(printf '%s' "$STRIPPED" | sed 's/"[^"]*"//g' 2>/dev/null || printf '%s' "$STRIPPED")
|
|
43
|
-
# Remove single-quoted strings
|
|
44
|
-
STRIPPED=$(printf '%s' "$STRIPPED" | sed "s/'[^']*'//g" 2>/dev/null || printf '%s' "$STRIPPED")
|
|
45
|
-
|
|
46
|
-
# --- Helper: check if pattern matches stripped command ---
|
|
47
|
-
matches() {
|
|
48
|
-
printf '%s' "$STRIPPED" | grep -qE "$1"
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
# --- Detect destructive commands (on stripped command only) ---
|
|
52
|
-
|
|
53
|
-
IS_RM=false
|
|
54
|
-
IS_RMDIR=false
|
|
55
|
-
IS_MV_PROJECT=false
|
|
56
|
-
IS_GIT_CLEAN=false
|
|
57
|
-
IS_RESET_HARD=false
|
|
58
|
-
IS_GIT_CHECKOUT_DOT=false
|
|
59
|
-
IS_GIT_RESTORE_DOT=false
|
|
60
|
-
IS_GIT_PUSH_FORCE=false
|
|
61
|
-
IS_DROP_TABLE=false
|
|
62
|
-
IS_DOCKER_RM=false
|
|
63
|
-
|
|
64
|
-
# rm (but not git rm which stages, doesn't destroy)
|
|
65
|
-
if matches '(^|\s|;|&&|\|\|)rm\s'; then
|
|
66
|
-
if ! matches '(^|\s)git\s+rm\s'; then
|
|
67
|
-
IS_RM=true
|
|
68
|
-
fi
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
# rmdir
|
|
72
|
-
if matches '(^|\s|;|&&|\|\|)rmdir\s'; then
|
|
73
|
-
IS_RMDIR=true
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
# mv of project directories or git repos
|
|
77
|
-
if matches '(^|\s|;|&&|\|\|)mv\s'; then
|
|
78
|
-
MV_TAIL=$(printf '%s' "$STRIPPED" | sed 's/^.*\bmv //' | sed 's/-[finv] //g')
|
|
79
|
-
if printf '%s' "$MV_TAIL" | grep -qE '(~/projects|\.git)'; then
|
|
80
|
-
IS_MV_PROJECT=true
|
|
81
|
-
fi
|
|
82
|
-
fi
|
|
83
|
-
|
|
84
|
-
# git clean
|
|
85
|
-
if matches '(^|\s|;|&&|\|\|)git\s+clean\b'; then
|
|
86
|
-
IS_GIT_CLEAN=true
|
|
87
|
-
fi
|
|
88
|
-
|
|
89
|
-
# git reset --hard
|
|
90
|
-
if matches '(^|\s|;|&&|\|\|)git\s+reset\s+--hard'; then
|
|
91
|
-
IS_RESET_HARD=true
|
|
92
|
-
fi
|
|
93
|
-
|
|
94
|
-
# git checkout -- .
|
|
95
|
-
if matches '(^|\s|;|&&|\|\|)git\s+checkout\s+--\s+\.'; then
|
|
96
|
-
IS_GIT_CHECKOUT_DOT=true
|
|
97
|
-
fi
|
|
98
|
-
|
|
99
|
-
# git restore .
|
|
100
|
-
if matches '(^|\s|;|&&|\|\|)git\s+restore\s+\.'; then
|
|
101
|
-
IS_GIT_RESTORE_DOT=true
|
|
102
|
-
fi
|
|
103
|
-
|
|
104
|
-
# git push --force / -f (but not --force-with-lease which is safer)
|
|
105
|
-
if matches '(^|\s|;|&&|\|\|)git\s+push\s'; then
|
|
106
|
-
if matches 'git\s+push\s.*--force([^-]|$)' || matches 'git\s+push\s+-f(\s|$)' || matches 'git\s+push\s.*\s-f(\s|$)'; then
|
|
107
|
-
IS_GIT_PUSH_FORCE=true
|
|
108
|
-
fi
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
# SQL drop table (case-insensitive)
|
|
112
|
-
if printf '%s' "$STRIPPED" | grep -qiE '(^|\s|;)drop\s+table'; then
|
|
113
|
-
IS_DROP_TABLE=true
|
|
114
|
-
fi
|
|
115
|
-
|
|
116
|
-
# docker rm / docker rmi
|
|
117
|
-
if matches '(^|\s|;|&&|\|\|)docker\s+(rm|rmi)\b'; then
|
|
118
|
-
IS_DOCKER_RM=true
|
|
119
|
-
fi
|
|
120
|
-
|
|
121
|
-
# --- Not a destructive command — let it through ---
|
|
122
|
-
|
|
123
|
-
if [ "$IS_RM" = false ] && [ "$IS_RMDIR" = false ] && [ "$IS_MV_PROJECT" = false ] && \
|
|
124
|
-
[ "$IS_GIT_CLEAN" = false ] && [ "$IS_RESET_HARD" = false ] && \
|
|
125
|
-
[ "$IS_GIT_CHECKOUT_DOT" = false ] && [ "$IS_GIT_RESTORE_DOT" = false ] && \
|
|
126
|
-
[ "$IS_GIT_PUSH_FORCE" = false ] && [ "$IS_DROP_TABLE" = false ] && \
|
|
127
|
-
[ "$IS_DOCKER_RM" = false ]; then
|
|
128
|
-
exit 0
|
|
129
|
-
fi
|
|
130
|
-
|
|
131
|
-
# --- Block: git clean ---
|
|
132
|
-
if [ "$IS_GIT_CLEAN" = true ]; then
|
|
133
|
-
jq -n '{
|
|
134
|
-
continue: false,
|
|
135
|
-
stopReason: "BLOCKED: git clean would remove untracked files. Use git stash --include-untracked to save them first, or ask the user to run git clean manually."
|
|
136
|
-
}'
|
|
137
|
-
exit 0
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
# --- Block: git reset --hard ---
|
|
141
|
-
if [ "$IS_RESET_HARD" = true ]; then
|
|
142
|
-
jq -n '{
|
|
143
|
-
continue: false,
|
|
144
|
-
stopReason: "BLOCKED: git reset --hard would discard uncommitted changes. Use git stash to save them first, or ask the user to run this manually."
|
|
145
|
-
}'
|
|
146
|
-
exit 0
|
|
147
|
-
fi
|
|
148
|
-
|
|
149
|
-
# --- Block: git checkout -- . ---
|
|
150
|
-
if [ "$IS_GIT_CHECKOUT_DOT" = true ]; then
|
|
151
|
-
jq -n '{
|
|
152
|
-
continue: false,
|
|
153
|
-
stopReason: "BLOCKED: git checkout -- . would discard all uncommitted changes. Use git stash to save them first, or ask the user to run this manually."
|
|
154
|
-
}'
|
|
155
|
-
exit 0
|
|
156
|
-
fi
|
|
157
|
-
|
|
158
|
-
# --- Block: git restore . ---
|
|
159
|
-
if [ "$IS_GIT_RESTORE_DOT" = true ]; then
|
|
160
|
-
jq -n '{
|
|
161
|
-
continue: false,
|
|
162
|
-
stopReason: "BLOCKED: git restore . would discard all uncommitted changes. Use git stash to save them first, or ask the user to run this manually."
|
|
163
|
-
}'
|
|
164
|
-
exit 0
|
|
165
|
-
fi
|
|
166
|
-
|
|
167
|
-
# --- Block: git push --force ---
|
|
168
|
-
if [ "$IS_GIT_PUSH_FORCE" = true ]; then
|
|
169
|
-
jq -n '{
|
|
170
|
-
continue: false,
|
|
171
|
-
stopReason: "BLOCKED: git push --force can overwrite remote history and cause data loss for collaborators. Use --force-with-lease instead, or ask the user to run this manually."
|
|
172
|
-
}'
|
|
173
|
-
exit 0
|
|
174
|
-
fi
|
|
175
|
-
|
|
176
|
-
# --- Block: mv of project directories ---
|
|
177
|
-
if [ "$IS_MV_PROJECT" = true ]; then
|
|
178
|
-
jq -n '{
|
|
179
|
-
continue: false,
|
|
180
|
-
stopReason: "BLOCKED: mv of a project directory or git repo detected. Moving project directories can cause data loss if the operation fails midway. Ask the user to run this manually, or use cp + verify + rm instead."
|
|
181
|
-
}'
|
|
182
|
-
exit 0
|
|
183
|
-
fi
|
|
184
|
-
|
|
185
|
-
# --- Block: rmdir ---
|
|
186
|
-
if [ "$IS_RMDIR" = true ]; then
|
|
187
|
-
jq -n '{
|
|
188
|
-
continue: false,
|
|
189
|
-
stopReason: "BLOCKED: rmdir detected. Removing directories can break project structure. Ask the user to confirm this operation manually."
|
|
190
|
-
}'
|
|
191
|
-
exit 0
|
|
192
|
-
fi
|
|
193
|
-
|
|
194
|
-
# --- Block: drop table ---
|
|
195
|
-
if [ "$IS_DROP_TABLE" = true ]; then
|
|
196
|
-
jq -n '{
|
|
197
|
-
continue: false,
|
|
198
|
-
stopReason: "BLOCKED: DROP TABLE detected. This would permanently destroy database data. Ask the user to run this SQL statement manually after confirming intent."
|
|
199
|
-
}'
|
|
200
|
-
exit 0
|
|
201
|
-
fi
|
|
202
|
-
|
|
203
|
-
# --- Block: docker rm / rmi ---
|
|
204
|
-
if [ "$IS_DOCKER_RM" = true ]; then
|
|
205
|
-
jq -n '{
|
|
206
|
-
continue: false,
|
|
207
|
-
stopReason: "BLOCKED: docker rm/rmi detected. Removing containers or images can cause data loss. Ask the user to run this manually."
|
|
208
|
-
}'
|
|
209
|
-
exit 0
|
|
210
|
-
fi
|
|
211
|
-
|
|
212
|
-
# --- Handle rm commands — copy to staging, then block ---
|
|
213
|
-
|
|
214
|
-
# Create timestamped staging directory
|
|
215
|
-
TIMESTAMP=$(date +%Y-%m-%d_%H%M%S)
|
|
216
|
-
STAGE_DIR="$STAGING_ROOT/$TIMESTAMP"
|
|
217
|
-
|
|
218
|
-
# Extract file paths from the rm command
|
|
219
|
-
# Strip rm and its flags, keeping only the file arguments
|
|
220
|
-
FILES=$(printf '%s' "$CMD" | sed 's/^.*\brm //' | sed 's/-[rRfivd]* //g' | tr ' ' '\n' | grep -v '^-' | grep -v '^$' || true)
|
|
221
|
-
|
|
222
|
-
if [ -z "$FILES" ]; then
|
|
223
|
-
jq -n '{
|
|
224
|
-
continue: false,
|
|
225
|
-
stopReason: "BLOCKED: rm command detected but could not parse file targets. Please specify files explicitly."
|
|
226
|
-
}'
|
|
227
|
-
exit 0
|
|
228
|
-
fi
|
|
229
|
-
|
|
230
|
-
STAGED_COUNT=0
|
|
231
|
-
STAGED_LIST=""
|
|
232
|
-
MISSING_COUNT=0
|
|
233
|
-
|
|
234
|
-
mkdir -p "$STAGE_DIR"
|
|
235
|
-
|
|
236
|
-
printf '%s\n' "$FILES" | while IFS= read -r filepath; do
|
|
237
|
-
# Expand path (handle ~, relative paths)
|
|
238
|
-
expanded=$(eval printf '%s' "$filepath" 2>/dev/null || printf '%s' "$filepath")
|
|
239
|
-
|
|
240
|
-
if [ -e "$expanded" ]; then
|
|
241
|
-
# Preserve directory structure in staging
|
|
242
|
-
target_dir="$STAGE_DIR/$(dirname "$expanded")"
|
|
243
|
-
mkdir -p "$target_dir"
|
|
244
|
-
# COPY instead of MOVE — originals stay intact, staging is a backup
|
|
245
|
-
if [ -d "$expanded" ]; then
|
|
246
|
-
# Use rsync if available (excludes node_modules/dist/.git), fall back to cp
|
|
247
|
-
if command -v rsync >/dev/null 2>&1; then
|
|
248
|
-
rsync -a --exclude='node_modules' --exclude='dist' --exclude='.git' "$expanded/" "$target_dir/$(basename "$expanded")/" 2>/dev/null
|
|
249
|
-
else
|
|
250
|
-
cp -R "$expanded" "$target_dir/" 2>/dev/null
|
|
251
|
-
fi
|
|
252
|
-
else
|
|
253
|
-
cp "$expanded" "$target_dir/" 2>/dev/null
|
|
254
|
-
fi
|
|
255
|
-
fi
|
|
256
|
-
done
|
|
257
|
-
|
|
258
|
-
# Count what was staged (check if staging dir has content)
|
|
259
|
-
if [ -d "$STAGE_DIR" ] && [ "$(ls -A "$STAGE_DIR" 2>/dev/null)" ]; then
|
|
260
|
-
STAGED_COUNT=$(find "$STAGE_DIR" -mindepth 1 -maxdepth 1 | wc -l | tr -d ' ')
|
|
261
|
-
fi
|
|
262
|
-
|
|
263
|
-
if [ "$STAGED_COUNT" -eq 0 ]; then
|
|
264
|
-
# All files were missing — let the rm fail naturally
|
|
265
|
-
rmdir "$STAGE_DIR" 2>/dev/null || true
|
|
266
|
-
exit 0
|
|
267
|
-
fi
|
|
268
|
-
|
|
269
|
-
jq -n \
|
|
270
|
-
--arg dir "$STAGE_DIR" \
|
|
271
|
-
'{
|
|
272
|
-
continue: false,
|
|
273
|
-
stopReason: ("BLOCKED & BACKED UP: Files copied to " + $dir + ". The originals are untouched. To proceed with deletion, ask the user to run the rm command manually.")
|
|
274
|
-
}'
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pre-built agent archetypes that pre-fill the wizard.
|
|
3
|
-
* Each archetype provides sensible defaults for role, description,
|
|
4
|
-
* domains, principles, skills, and greeting — so the user can
|
|
5
|
-
* scaffold a full agent with minimal typing.
|
|
6
|
-
*/
|
|
7
|
-
export interface Archetype {
|
|
8
|
-
value: string;
|
|
9
|
-
label: string;
|
|
10
|
-
hint: string;
|
|
11
|
-
tier: 'free' | 'premium';
|
|
12
|
-
defaults: {
|
|
13
|
-
role: string;
|
|
14
|
-
description: string;
|
|
15
|
-
domains: string[];
|
|
16
|
-
principles: string[];
|
|
17
|
-
skills: string[];
|
|
18
|
-
tone: 'precise' | 'mentor' | 'pragmatic';
|
|
19
|
-
greetingTemplate: (name: string) => string;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
export declare const ARCHETYPES: Archetype[];
|