@miphamai/cli 0.5.12 → 0.6.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/bin/mipham.ts +11 -0
- package/dist/mipham +0 -0
- package/package.json +1 -1
- package/skills/standard/web-access.SKILL.md +54 -0
- package/src/commands/loop-scaffold.ts +342 -0
- package/src/config/loader.ts +65 -1
- package/src/core/engine.ts +1 -0
- package/src/index.tsx +5 -0
- package/src/plugin/plugin-registry.ts +69 -0
- package/src/providers/openai-compat.ts +85 -15
- package/src/shared/constants.ts +9 -0
- package/src/skills/registry.ts +26 -6
- package/src/ui/app.tsx +5 -1
- package/src/ui/commands.ts +765 -6
package/src/ui/commands.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { QueryEngine } from '../core/engine'
|
|
8
8
|
import type { MiphamConfig } from '../shared/index.ts'
|
|
9
9
|
import type { SkillsLoader } from '../skills/loader'
|
|
10
|
+
import type { PluginManager } from '../plugin/plugin-manager'
|
|
10
11
|
import { McpClient } from '../mcp/client'
|
|
11
12
|
import { NPM_INSTALL_COMMAND, NPM_UPDATE_COMMAND, PACKAGE_VERSION } from '../shared/index.ts'
|
|
12
13
|
|
|
@@ -23,6 +24,7 @@ export interface CommandContext {
|
|
|
23
24
|
setFocusMode: (on: boolean) => void
|
|
24
25
|
setGoal: (text: string) => void
|
|
25
26
|
skillsLoader?: SkillsLoader
|
|
27
|
+
pluginManager?: PluginManager
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface CommandResult {
|
|
@@ -71,6 +73,10 @@ const helpCmd: CommandHandler = (ctx) => {
|
|
|
71
73
|
/rename <name> Rename current session
|
|
72
74
|
/goal <text> Set session goal
|
|
73
75
|
/recap Summarize session so far
|
|
76
|
+
/summary Generate session summary
|
|
77
|
+
/stats Session usage statistics
|
|
78
|
+
/files List files in CWD
|
|
79
|
+
/cd <path> Change working directory
|
|
74
80
|
/export Export conversation to file
|
|
75
81
|
/doctor System diagnostics
|
|
76
82
|
/resume List saved sessions
|
|
@@ -103,6 +109,12 @@ const helpCmd: CommandHandler = (ctx) => {
|
|
|
103
109
|
/remove-skill Remove an installed skill
|
|
104
110
|
/commands List all slash commands
|
|
105
111
|
/mcp MCP server status
|
|
112
|
+
/plugins List installed plugins
|
|
113
|
+
/browse-plugins Browse community plugins
|
|
114
|
+
/install-plugin Install plugin from npm or path
|
|
115
|
+
/remove-plugin Remove an installed plugin
|
|
116
|
+
/plugin-enable Enable a disabled plugin
|
|
117
|
+
/plugin-disable Disable an enabled plugin
|
|
106
118
|
|
|
107
119
|
── Workflow ────────────────────────
|
|
108
120
|
/plan Enter plan mode (read-only)
|
|
@@ -115,8 +127,21 @@ const helpCmd: CommandHandler = (ctx) => {
|
|
|
115
127
|
/diff Show git diff
|
|
116
128
|
/workflows List workflow scripts
|
|
117
129
|
/loop <int> <p> Run prompt on interval
|
|
130
|
+
/loop init Scaffold .mipham/ vault structure
|
|
131
|
+
/batch Apply changes across files
|
|
132
|
+
/hooks Manage lifecycle hook scripts
|
|
118
133
|
/schedule View scheduled tasks [stub]
|
|
119
134
|
|
|
135
|
+
── Git & GitHub ─────────────────────
|
|
136
|
+
/commit Review staged changes + commit
|
|
137
|
+
/push Push current branch
|
|
138
|
+
/pr Create a pull request
|
|
139
|
+
/issue File a GitHub issue
|
|
140
|
+
|
|
141
|
+
── Code Quality ─────────────────────
|
|
142
|
+
/simplify Review for reuse, quality, simplification
|
|
143
|
+
/lint Run project linter
|
|
144
|
+
|
|
120
145
|
── Project ─────────────────────────
|
|
121
146
|
/init Initialize .mipham config
|
|
122
147
|
/setup Guided project setup wizard
|
|
@@ -618,8 +643,9 @@ const browseSkillsCmd: CommandHandler = async () => {
|
|
|
618
643
|
lines.push(` ${cat}:`)
|
|
619
644
|
for (const name of names) {
|
|
620
645
|
const entry = available.find((s) => s.name === name)!
|
|
621
|
-
const marker = installed.has(name) ? '✅' : '⬜'
|
|
622
|
-
|
|
646
|
+
const marker = entry.builtin ? '🏠' : installed.has(name) ? '✅' : '⬜'
|
|
647
|
+
const status = entry.builtin ? ' (built-in)' : installed.has(name) ? ' (installed)' : ''
|
|
648
|
+
lines.push(` ${marker} /${name.padEnd(26)} ${entry.description}${status}`)
|
|
623
649
|
}
|
|
624
650
|
lines.push('')
|
|
625
651
|
}
|
|
@@ -666,6 +692,181 @@ const removeSkillCmd: CommandHandler = async (_ctx, args) => {
|
|
|
666
692
|
}
|
|
667
693
|
}
|
|
668
694
|
|
|
695
|
+
// ═══════════════════════════════════════════════════════════════
|
|
696
|
+
// Plugins — Community plugin marketplace + local management
|
|
697
|
+
// ═══════════════════════════════════════════════════════════════
|
|
698
|
+
|
|
699
|
+
const pluginsCmd: CommandHandler = (ctx) => {
|
|
700
|
+
const manager = ctx.pluginManager
|
|
701
|
+
if (!manager) {
|
|
702
|
+
return { content: 'PluginManager not available in this session.' }
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const plugins = manager.list()
|
|
706
|
+
if (plugins.length === 0) {
|
|
707
|
+
return {
|
|
708
|
+
content: [
|
|
709
|
+
'── Installed Plugins ──',
|
|
710
|
+
'',
|
|
711
|
+
'No plugins installed.',
|
|
712
|
+
'',
|
|
713
|
+
'Install plugins:',
|
|
714
|
+
' /install-plugin <npm-package> Install from npm registry',
|
|
715
|
+
' /browse-plugins Browse community plugins',
|
|
716
|
+
' mipham plugin install <path> Install from local directory',
|
|
717
|
+
].join('\n'),
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const lines: string[] = [
|
|
722
|
+
'── Installed Plugins ──',
|
|
723
|
+
'',
|
|
724
|
+
...plugins.map((p) => {
|
|
725
|
+
const status = p.enabled ? '✅ enabled' : '⛔ disabled'
|
|
726
|
+
return ` ${p.name.padEnd(24)} v${p.version.padEnd(10)} ${status} ${p.installedAt ? new Date(p.installedAt).toLocaleDateString() : ''}`
|
|
727
|
+
}),
|
|
728
|
+
'',
|
|
729
|
+
`${plugins.length} plugin(s) installed.`,
|
|
730
|
+
'',
|
|
731
|
+
'Manage: /install-plugin | /remove-plugin | /plugin-enable | /plugin-disable',
|
|
732
|
+
]
|
|
733
|
+
return { content: lines.join('\n') }
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const browsePluginsCmd: CommandHandler = async (ctx) => {
|
|
737
|
+
const { getAvailablePlugins } = await import('../plugin/plugin-registry')
|
|
738
|
+
const available = getAvailablePlugins()
|
|
739
|
+
const installed = new Set((ctx.pluginManager?.list() ?? []).map((p) => p.name))
|
|
740
|
+
|
|
741
|
+
const categories = new Map<string, string[]>()
|
|
742
|
+
for (const p of available) {
|
|
743
|
+
const list = categories.get(p.category) || []
|
|
744
|
+
list.push(p.name)
|
|
745
|
+
categories.set(p.category, list)
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const lines: string[] = [
|
|
749
|
+
'── Community Plugins ──',
|
|
750
|
+
'',
|
|
751
|
+
`${available.length} plugins available · ${installed.size} installed`,
|
|
752
|
+
'',
|
|
753
|
+
]
|
|
754
|
+
|
|
755
|
+
for (const [cat, names] of categories) {
|
|
756
|
+
lines.push(` ${cat}:`)
|
|
757
|
+
for (const name of names) {
|
|
758
|
+
const entry = available.find((p) => p.name === name)!
|
|
759
|
+
const marker = installed.has(name) ? '✅' : '⬜'
|
|
760
|
+
lines.push(` ${marker} ${name.padEnd(22)} ${entry.description}`)
|
|
761
|
+
lines.push(` npm: ${entry.npmPackage}`)
|
|
762
|
+
}
|
|
763
|
+
lines.push('')
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
lines.push('Install: /install-plugin <npm-package>')
|
|
767
|
+
lines.push('Remove: /remove-plugin <name>')
|
|
768
|
+
|
|
769
|
+
return { content: lines.join('\n') }
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const installPluginCmd: CommandHandler = async (ctx, args) => {
|
|
773
|
+
const target = args[0]
|
|
774
|
+
if (!target) {
|
|
775
|
+
return {
|
|
776
|
+
content: [
|
|
777
|
+
'Usage: /install-plugin <npm-package> or /install-plugin <local-path>',
|
|
778
|
+
'',
|
|
779
|
+
'Examples:',
|
|
780
|
+
' /install-plugin @roomi-fields/notebooklm-mcp',
|
|
781
|
+
' /install-plugin mipham-plugin-security',
|
|
782
|
+
' /install-plugin ~/my-custom-plugin/',
|
|
783
|
+
'',
|
|
784
|
+
'Use /browse-plugins to see available community plugins.',
|
|
785
|
+
].join('\n'),
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const manager = ctx.pluginManager
|
|
790
|
+
if (!manager) {
|
|
791
|
+
return { content: '❌ PluginManager not available in this session.' }
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Determine if it's a local path or npm package
|
|
795
|
+
const isLocalPath = target.startsWith('./') || target.startsWith('/') || target.startsWith('~/')
|
|
796
|
+
|
|
797
|
+
let result: { success: boolean; message: string }
|
|
798
|
+
|
|
799
|
+
if (isLocalPath) {
|
|
800
|
+
const { resolve } = await import('node:path')
|
|
801
|
+
const resolved = resolve(target.replace(/^~/, process.env.HOME || '~'))
|
|
802
|
+
result = manager.install(resolved)
|
|
803
|
+
} else {
|
|
804
|
+
// Treat as npm package name
|
|
805
|
+
result = manager.installFromNpm(target)
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
return {
|
|
809
|
+
content: result.success ? `✅ ${result.message}` : `❌ ${result.message}`,
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const removePluginCmd: CommandHandler = (ctx, args) => {
|
|
814
|
+
const name = args[0]
|
|
815
|
+
if (!name) {
|
|
816
|
+
return { content: 'Usage: /remove-plugin <name>\n\nUse /plugins to see installed plugins.' }
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const manager = ctx.pluginManager
|
|
820
|
+
if (!manager) {
|
|
821
|
+
return { content: '❌ PluginManager not available in this session.' }
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const ok = manager.remove(name)
|
|
825
|
+
return {
|
|
826
|
+
content: ok
|
|
827
|
+
? `✅ Plugin "${name}" removed.`
|
|
828
|
+
: `❌ Plugin "${name}" not found. Use /plugins to list installed plugins.`,
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const pluginEnableCmd: CommandHandler = (ctx, args) => {
|
|
833
|
+
const name = args[0]
|
|
834
|
+
if (!name) {
|
|
835
|
+
return { content: 'Usage: /plugin-enable <name>\n\nUse /plugins to see installed plugins.' }
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const manager = ctx.pluginManager
|
|
839
|
+
if (!manager) {
|
|
840
|
+
return { content: '❌ PluginManager not available in this session.' }
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const ok = manager.enable(name)
|
|
844
|
+
return {
|
|
845
|
+
content: ok
|
|
846
|
+
? `✅ Plugin "${name}" enabled.`
|
|
847
|
+
: `❌ Plugin "${name}" not found. Use /plugins to list installed plugins.`,
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const pluginDisableCmd: CommandHandler = (ctx, args) => {
|
|
852
|
+
const name = args[0]
|
|
853
|
+
if (!name) {
|
|
854
|
+
return { content: 'Usage: /plugin-disable <name>\n\nUse /plugins to see installed plugins.' }
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const manager = ctx.pluginManager
|
|
858
|
+
if (!manager) {
|
|
859
|
+
return { content: '❌ PluginManager not available in this session.' }
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const ok = manager.disable(name)
|
|
863
|
+
return {
|
|
864
|
+
content: ok
|
|
865
|
+
? `✅ Plugin "${name}" disabled.`
|
|
866
|
+
: `❌ Plugin "${name}" not found. Use /plugins to list installed plugins.`,
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
669
870
|
// ═══════════════════════════════════════════════════════════════
|
|
670
871
|
// Phase 1 — History & Checkpoint Commands
|
|
671
872
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -887,13 +1088,74 @@ const branchCmd: CommandHandler = (ctx, args) => {
|
|
|
887
1088
|
}
|
|
888
1089
|
}
|
|
889
1090
|
|
|
890
|
-
const loopCmd: CommandHandler = (_ctx, args) => {
|
|
1091
|
+
const loopCmd: CommandHandler = async (_ctx, args) => {
|
|
1092
|
+
const sub = args[0]
|
|
1093
|
+
|
|
1094
|
+
// ── /loop init — scaffold project vault ──
|
|
1095
|
+
if (sub === 'init' || sub === 'scaffold') {
|
|
1096
|
+
const targetPath = args[1] || process.cwd()
|
|
1097
|
+
const { scaffoldLoopKit } = await import('../commands/loop-scaffold')
|
|
1098
|
+
const { created, skipped } = scaffoldLoopKit(targetPath)
|
|
1099
|
+
const { resolve } = await import('node:path')
|
|
1100
|
+
const resolved = resolve(targetPath.replace(/^~/, process.env.HOME || '~'))
|
|
1101
|
+
|
|
1102
|
+
const lines: string[] = ['── LoopKit Vault Created ──', '', `Location: ${resolved}`, '']
|
|
1103
|
+
|
|
1104
|
+
if (created.length > 0) {
|
|
1105
|
+
lines.push(`Created (${created.length}):`)
|
|
1106
|
+
for (const c of created.slice(0, 12)) {
|
|
1107
|
+
const rel = c.replace(resolved + '/', '')
|
|
1108
|
+
lines.push(` ✅ ${rel}`)
|
|
1109
|
+
}
|
|
1110
|
+
if (created.length > 12) {
|
|
1111
|
+
lines.push(` ... and ${created.length - 12} more`)
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (skipped.length > 0) {
|
|
1116
|
+
lines.push('')
|
|
1117
|
+
lines.push(`Skipped (${skipped.length} — already exist):`)
|
|
1118
|
+
for (const s of skipped.slice(0, 5)) {
|
|
1119
|
+
const rel = s.replace(resolved + '/', '')
|
|
1120
|
+
lines.push(` ⏭ ${rel}`)
|
|
1121
|
+
}
|
|
1122
|
+
if (skipped.length > 5) {
|
|
1123
|
+
lines.push(` ... and ${skipped.length - 5} more`)
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
lines.push('')
|
|
1128
|
+
lines.push('── Structure ──')
|
|
1129
|
+
lines.push('')
|
|
1130
|
+
lines.push(' .mipham/ Mipham Code config directory')
|
|
1131
|
+
lines.push(' ├── CLAUDE.md Project AI instructions')
|
|
1132
|
+
lines.push(' ├── settings.json Permission & hook settings')
|
|
1133
|
+
lines.push(' ├── hooks/ Lifecycle hook scripts (3)')
|
|
1134
|
+
lines.push(' ├── agents/ Custom sub-agents')
|
|
1135
|
+
lines.push(' └── skills/ 9 domain-sorted skill directories')
|
|
1136
|
+
lines.push('')
|
|
1137
|
+
lines.push(' .mcp.json MCP server configuration')
|
|
1138
|
+
lines.push(' MEMORY.md AI persistent memory')
|
|
1139
|
+
lines.push(' run.sh / install.sh Project scripts')
|
|
1140
|
+
lines.push('')
|
|
1141
|
+
lines.push('Edit .mipham/CLAUDE.md to customize AI behavior.')
|
|
1142
|
+
lines.push('Add skills to .mipham/skills/<domain>/ directories.')
|
|
1143
|
+
lines.push('Configure MCP servers in .mcp.json.')
|
|
1144
|
+
lines.push('')
|
|
1145
|
+
lines.push('/loop init <path> to scaffold in a different location.')
|
|
1146
|
+
|
|
1147
|
+
return { content: lines.join('\n') }
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// ── /loop <interval> <prompt> — recurring prompt ──
|
|
891
1151
|
if (args.length < 2) {
|
|
892
1152
|
return {
|
|
893
1153
|
content: stripIndent`
|
|
894
1154
|
Usage: /loop <interval> <prompt>
|
|
1155
|
+
or: /loop init [path] Scaffold project vault structure
|
|
895
1156
|
|
|
896
|
-
Run a prompt repeatedly on a set interval
|
|
1157
|
+
Run a prompt repeatedly on a set interval, or initialize
|
|
1158
|
+
a LoopKit Vault project structure with Mipham Code config.
|
|
897
1159
|
|
|
898
1160
|
Interval formats:
|
|
899
1161
|
10s — 10 seconds
|
|
@@ -902,10 +1164,10 @@ const loopCmd: CommandHandler = (_ctx, args) => {
|
|
|
902
1164
|
|
|
903
1165
|
Example:
|
|
904
1166
|
/loop 5m check the deploy status
|
|
905
|
-
/loop
|
|
1167
|
+
/loop init Create vault in current directory
|
|
1168
|
+
/loop init ~/my-project Create vault in specific directory
|
|
906
1169
|
|
|
907
1170
|
Note: Continuous looping requires session persistence, coming in a future release.
|
|
908
|
-
For now, this sets up the intent — you'll be prompted to confirm each iteration.
|
|
909
1171
|
`,
|
|
910
1172
|
}
|
|
911
1173
|
}
|
|
@@ -1016,6 +1278,442 @@ const doctorCmd: CommandHandler = async (ctx) => {
|
|
|
1016
1278
|
return { content: lines.join('\n') }
|
|
1017
1279
|
}
|
|
1018
1280
|
|
|
1281
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1282
|
+
// GitHub & Git Workflow Commands (Claude Code parity)
|
|
1283
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1284
|
+
|
|
1285
|
+
const commitCmd: CommandHandler = async () => {
|
|
1286
|
+
try {
|
|
1287
|
+
const { execSync } = await import('node:child_process')
|
|
1288
|
+
const diff = execSync('git diff --cached --stat', { encoding: 'utf-8', timeout: 5000 }).trim()
|
|
1289
|
+
const unstaged = execSync('git diff --name-only', { encoding: 'utf-8', timeout: 5000 }).trim()
|
|
1290
|
+
|
|
1291
|
+
if (!diff) {
|
|
1292
|
+
let msg = '── Git Commit ──\n\nNo staged changes found.\n\n'
|
|
1293
|
+
if (unstaged) {
|
|
1294
|
+
msg +=
|
|
1295
|
+
'Unstaged changes exist:\n' +
|
|
1296
|
+
unstaged
|
|
1297
|
+
.split('\n')
|
|
1298
|
+
.map((f) => ' • ' + f)
|
|
1299
|
+
.join('\n') +
|
|
1300
|
+
'\n\n'
|
|
1301
|
+
}
|
|
1302
|
+
msg +=
|
|
1303
|
+
'Run: git add <files> to stage changes first.\nOr type: "commit these changes" for AI assistance.'
|
|
1304
|
+
return { content: msg }
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
return {
|
|
1308
|
+
content: [
|
|
1309
|
+
'── Git Commit ──',
|
|
1310
|
+
'',
|
|
1311
|
+
'Staged changes:',
|
|
1312
|
+
diff,
|
|
1313
|
+
'',
|
|
1314
|
+
'To generate a commit message, type: "write a commit message for these changes"',
|
|
1315
|
+
'To commit: git commit -m "your message"',
|
|
1316
|
+
'',
|
|
1317
|
+
'Tip: Use Conventional Commits format (feat:, fix:, chore:, docs:)',
|
|
1318
|
+
].join('\n'),
|
|
1319
|
+
}
|
|
1320
|
+
} catch {
|
|
1321
|
+
return { content: '── Git Commit ──\n\nCould not run git. Are you in a git repository?' }
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
const pushCmd: CommandHandler = async () => {
|
|
1326
|
+
try {
|
|
1327
|
+
const { execSync } = await import('node:child_process')
|
|
1328
|
+
const branch = execSync('git branch --show-current', {
|
|
1329
|
+
encoding: 'utf-8',
|
|
1330
|
+
timeout: 3000,
|
|
1331
|
+
}).trim()
|
|
1332
|
+
const ahead = execSync(`git rev-list --count origin/${branch}..HEAD 2>/dev/null || echo 0`, {
|
|
1333
|
+
encoding: 'utf-8',
|
|
1334
|
+
timeout: 3000,
|
|
1335
|
+
}).trim()
|
|
1336
|
+
|
|
1337
|
+
return {
|
|
1338
|
+
content: [
|
|
1339
|
+
'── Git Push ──',
|
|
1340
|
+
'',
|
|
1341
|
+
`Branch: ${branch || '(unknown)'}`,
|
|
1342
|
+
`Commits ahead of remote: ${ahead}`,
|
|
1343
|
+
'',
|
|
1344
|
+
ahead !== '0'
|
|
1345
|
+
? `Ready to push ${ahead} commit(s). Run: git push origin ${branch}`
|
|
1346
|
+
: 'Nothing to push (up to date).',
|
|
1347
|
+
'',
|
|
1348
|
+
'Or type: "push my changes" for AI assistance.',
|
|
1349
|
+
].join('\n'),
|
|
1350
|
+
}
|
|
1351
|
+
} catch {
|
|
1352
|
+
return { content: '── Git Push ──\n\nCould not determine git status.' }
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
const prCmd: CommandHandler = async () => {
|
|
1357
|
+
try {
|
|
1358
|
+
const { execSync } = await import('node:child_process')
|
|
1359
|
+
const branch = execSync('git branch --show-current', {
|
|
1360
|
+
encoding: 'utf-8',
|
|
1361
|
+
timeout: 3000,
|
|
1362
|
+
}).trim()
|
|
1363
|
+
const mainBranch =
|
|
1364
|
+
execSync('git remote show origin 2>/dev/null | grep "HEAD branch" | cut -d: -f2', {
|
|
1365
|
+
encoding: 'utf-8',
|
|
1366
|
+
timeout: 3000,
|
|
1367
|
+
}).trim() || 'main'
|
|
1368
|
+
|
|
1369
|
+
const diff = execSync(
|
|
1370
|
+
`git diff --stat origin/${mainBranch}...HEAD 2>/dev/null || git diff --stat ${mainBranch}...HEAD 2>/dev/null || echo ""`,
|
|
1371
|
+
{ encoding: 'utf-8', timeout: 5000 },
|
|
1372
|
+
).trim()
|
|
1373
|
+
|
|
1374
|
+
const commits = execSync(
|
|
1375
|
+
`git log --oneline origin/${mainBranch}..HEAD 2>/dev/null || git log --oneline ${mainBranch}..HEAD 2>/dev/null || echo ""`,
|
|
1376
|
+
{ encoding: 'utf-8', timeout: 5000 },
|
|
1377
|
+
).trim()
|
|
1378
|
+
|
|
1379
|
+
return {
|
|
1380
|
+
content: [
|
|
1381
|
+
'── Create Pull Request ──',
|
|
1382
|
+
'',
|
|
1383
|
+
`Branch: ${branch} → ${mainBranch}`,
|
|
1384
|
+
'',
|
|
1385
|
+
commits
|
|
1386
|
+
? `Commits:\n${commits
|
|
1387
|
+
.split('\n')
|
|
1388
|
+
.map((c) => ' ' + c)
|
|
1389
|
+
.join('\n')}`
|
|
1390
|
+
: 'No commits ahead.',
|
|
1391
|
+
'',
|
|
1392
|
+
diff ? `Changes:\n${diff}` : '',
|
|
1393
|
+
'',
|
|
1394
|
+
'To create PR: type "create a pull request" for AI assistance.',
|
|
1395
|
+
'Or use: gh pr create --base ' + mainBranch + ' --head ' + branch,
|
|
1396
|
+
].join('\n'),
|
|
1397
|
+
}
|
|
1398
|
+
} catch {
|
|
1399
|
+
return { content: '── Create Pull Request ──\n\nCould not determine PR context.' }
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
const issueCmd: CommandHandler = async () => {
|
|
1404
|
+
return {
|
|
1405
|
+
content: [
|
|
1406
|
+
'── GitHub Issues ──',
|
|
1407
|
+
'',
|
|
1408
|
+
'To file an issue, type: "create a GitHub issue for [description]"',
|
|
1409
|
+
'',
|
|
1410
|
+
'Quick commands:',
|
|
1411
|
+
' gh issue create --title "..." --body "..."',
|
|
1412
|
+
' gh issue list',
|
|
1413
|
+
'',
|
|
1414
|
+
'Use /github-ops skill for advanced GitHub automation.',
|
|
1415
|
+
].join('\n'),
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1420
|
+
// Code Quality Commands (Claude Code parity)
|
|
1421
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1422
|
+
|
|
1423
|
+
const simplifyCmd: CommandHandler = async () => {
|
|
1424
|
+
try {
|
|
1425
|
+
const { execSync } = await import('node:child_process')
|
|
1426
|
+
const diff = execSync('git diff --stat', { encoding: 'utf-8', timeout: 5000 }).trim()
|
|
1427
|
+
const changed = execSync('git diff --name-only', { encoding: 'utf-8', timeout: 3000 }).trim()
|
|
1428
|
+
|
|
1429
|
+
if (!diff) {
|
|
1430
|
+
return {
|
|
1431
|
+
content:
|
|
1432
|
+
'── Simplify ──\n\nNo uncommitted changes to review.\n\nRun /simplify after making changes to review for reuse, simplification, and quality improvements.',
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
return {
|
|
1437
|
+
content: [
|
|
1438
|
+
'── Simplify Review ──',
|
|
1439
|
+
'',
|
|
1440
|
+
'Changed files:',
|
|
1441
|
+
diff,
|
|
1442
|
+
'',
|
|
1443
|
+
'To run simplification review, type: "review these changes for simplification and quality"',
|
|
1444
|
+
'',
|
|
1445
|
+
'This will check for:',
|
|
1446
|
+
' • Code reuse opportunities',
|
|
1447
|
+
' • Overly complex logic',
|
|
1448
|
+
' • Dead code removal',
|
|
1449
|
+
' • Style and consistency',
|
|
1450
|
+
'',
|
|
1451
|
+
`Files to review: ${changed.split('\n').length}`,
|
|
1452
|
+
].join('\n'),
|
|
1453
|
+
}
|
|
1454
|
+
} catch {
|
|
1455
|
+
return { content: '── Simplify ──\n\nCould not detect changes. Are you in a git repository?' }
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
const lintCmd: CommandHandler = async () => {
|
|
1460
|
+
try {
|
|
1461
|
+
const { execSync } = await import('node:child_process')
|
|
1462
|
+
const { existsSync } = await import('node:fs')
|
|
1463
|
+
const { join } = await import('node:path')
|
|
1464
|
+
const cwd = process.cwd()
|
|
1465
|
+
|
|
1466
|
+
const hasPackageJson = existsSync(join(cwd, 'package.json'))
|
|
1467
|
+
|
|
1468
|
+
let lintOutput = ''
|
|
1469
|
+
if (hasPackageJson) {
|
|
1470
|
+
try {
|
|
1471
|
+
lintOutput = execSync(
|
|
1472
|
+
'npx eslint . --ext .ts,.tsx,.js,.jsx --max-warnings 0 2>&1 || true',
|
|
1473
|
+
{
|
|
1474
|
+
encoding: 'utf-8',
|
|
1475
|
+
timeout: 30000,
|
|
1476
|
+
cwd,
|
|
1477
|
+
},
|
|
1478
|
+
).trim()
|
|
1479
|
+
} catch {
|
|
1480
|
+
lintOutput = '(ESLint not configured or not found)'
|
|
1481
|
+
}
|
|
1482
|
+
} else {
|
|
1483
|
+
lintOutput = '(No package.json found — not a Node.js project)'
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
const lines = lintOutput.split('\n').slice(0, 40)
|
|
1487
|
+
const truncated = lines.length >= 40 ? lines.join('\n') + '\n... (truncated)' : lines.join('\n')
|
|
1488
|
+
|
|
1489
|
+
return {
|
|
1490
|
+
content: [
|
|
1491
|
+
'── Lint Results ──',
|
|
1492
|
+
'',
|
|
1493
|
+
truncated || '(no issues found)',
|
|
1494
|
+
'',
|
|
1495
|
+
hasPackageJson
|
|
1496
|
+
? 'To fix: type "fix the lint errors"'
|
|
1497
|
+
: 'Set up linting: https://mipham.ai/code/docs/linting',
|
|
1498
|
+
].join('\n'),
|
|
1499
|
+
}
|
|
1500
|
+
} catch {
|
|
1501
|
+
return {
|
|
1502
|
+
content: '── Lint ──\n\nCould not run linter. Ensure ESLint is installed and configured.',
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1508
|
+
// Session Enhancement Commands (Claude Code parity)
|
|
1509
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1510
|
+
|
|
1511
|
+
const filesCmd: CommandHandler = async () => {
|
|
1512
|
+
const { readdirSync, statSync } = await import('node:fs')
|
|
1513
|
+
const { join } = await import('node:path')
|
|
1514
|
+
const cwd = process.cwd()
|
|
1515
|
+
|
|
1516
|
+
try {
|
|
1517
|
+
const entries = readdirSync(cwd, { withFileTypes: true })
|
|
1518
|
+
const items = entries
|
|
1519
|
+
.filter((e) => !e.name.startsWith('.') || e.name === '.mipham' || e.name === '.mcp.json')
|
|
1520
|
+
.slice(0, 40)
|
|
1521
|
+
.map((e) => {
|
|
1522
|
+
const icon = e.isDirectory() ? '📁' : '📄'
|
|
1523
|
+
try {
|
|
1524
|
+
const stat = statSync(join(cwd, e.name))
|
|
1525
|
+
const size = stat.isFile() ? ` ${(stat.size / 1024).toFixed(1)}KB` : ''
|
|
1526
|
+
return ` ${icon} ${e.name}${size}`
|
|
1527
|
+
} catch {
|
|
1528
|
+
return ` ${icon} ${e.name}`
|
|
1529
|
+
}
|
|
1530
|
+
})
|
|
1531
|
+
|
|
1532
|
+
return {
|
|
1533
|
+
content: [
|
|
1534
|
+
'── Project Files ──',
|
|
1535
|
+
'',
|
|
1536
|
+
`CWD: ${cwd}`,
|
|
1537
|
+
'',
|
|
1538
|
+
...items,
|
|
1539
|
+
entries.length > 40 ? ` ... and ${entries.length - 40} more files` : '',
|
|
1540
|
+
'',
|
|
1541
|
+
'Use Read/Glob/Grep tools to examine files.',
|
|
1542
|
+
].join('\n'),
|
|
1543
|
+
}
|
|
1544
|
+
} catch {
|
|
1545
|
+
return { content: '── Files ──\n\nCould not read directory.' }
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
const statsCmd: CommandHandler = (ctx) => {
|
|
1550
|
+
const c = ctx.engine.getContext()
|
|
1551
|
+
const msgs = c.getMessages()
|
|
1552
|
+
const tokens = c.getEstimatedTokens()
|
|
1553
|
+
const tools = ctx.engine.getTools()
|
|
1554
|
+
|
|
1555
|
+
const userMsgs = msgs.filter((m) => m.role === 'user').length
|
|
1556
|
+
const assistantMsgs = msgs.filter((m) => m.role === 'assistant').length
|
|
1557
|
+
const systemMsgs = msgs.filter((m) => m.role === 'system').length
|
|
1558
|
+
|
|
1559
|
+
return {
|
|
1560
|
+
content: [
|
|
1561
|
+
'── Session Stats ──',
|
|
1562
|
+
'',
|
|
1563
|
+
`Messages: ${msgs.length} (${userMsgs} user, ${assistantMsgs} AI, ${systemMsgs} system)`,
|
|
1564
|
+
`Tokens: ~${tokens.toLocaleString()} / 200,000`,
|
|
1565
|
+
`Tools available: ${tools.size}`,
|
|
1566
|
+
`Provider: ${ctx.providerId}`,
|
|
1567
|
+
`Model: ${ctx.modelId}`,
|
|
1568
|
+
`Permission: ${ctx.config.permission}`,
|
|
1569
|
+
'',
|
|
1570
|
+
`Usage: ${((tokens / 200_000) * 100).toFixed(1)}% of context window`,
|
|
1571
|
+
].join('\n'),
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
const summaryCmd: CommandHandler = (ctx) => {
|
|
1576
|
+
const c = ctx.engine.getContext()
|
|
1577
|
+
const msgs = c.getMessages()
|
|
1578
|
+
|
|
1579
|
+
if (msgs.length === 0) {
|
|
1580
|
+
return { content: '── Summary ──\n\nNo conversation to summarize.' }
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
const userMsgs = msgs
|
|
1584
|
+
.filter((m) => m.role === 'user' && typeof m.content === 'string')
|
|
1585
|
+
.slice(0, 5)
|
|
1586
|
+
.map((m) => {
|
|
1587
|
+
const text = typeof m.content === 'string' ? m.content : ''
|
|
1588
|
+
return text.length > 100 ? text.slice(0, 100) + '...' : text
|
|
1589
|
+
})
|
|
1590
|
+
|
|
1591
|
+
return {
|
|
1592
|
+
content: [
|
|
1593
|
+
'── Session Summary ──',
|
|
1594
|
+
'',
|
|
1595
|
+
`Total messages: ${msgs.length}`,
|
|
1596
|
+
`Est. tokens: ~${c.getEstimatedTokens().toLocaleString()}`,
|
|
1597
|
+
'',
|
|
1598
|
+
'Recent topics:',
|
|
1599
|
+
...userMsgs.map((t, i) => ` ${i + 1}. ${t}`),
|
|
1600
|
+
'',
|
|
1601
|
+
'Use /export to save, /clear to reset.',
|
|
1602
|
+
].join('\n'),
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
const cdCmd: CommandHandler = async (_ctx, args) => {
|
|
1607
|
+
const target = args[0]
|
|
1608
|
+
if (!target) {
|
|
1609
|
+
return {
|
|
1610
|
+
content: [
|
|
1611
|
+
'Usage: /cd <path>',
|
|
1612
|
+
'',
|
|
1613
|
+
'Change the session working directory.',
|
|
1614
|
+
'Example: /cd ~/projects/my-app',
|
|
1615
|
+
'',
|
|
1616
|
+
`Current: ${process.cwd()}`,
|
|
1617
|
+
].join('\n'),
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
const { existsSync } = await import('node:fs')
|
|
1622
|
+
const { resolve } = await import('node:path')
|
|
1623
|
+
const resolved = resolve(target.replace(/^~/, process.env.HOME || '~'))
|
|
1624
|
+
|
|
1625
|
+
if (!existsSync(resolved)) {
|
|
1626
|
+
return { content: `❌ Directory not found: ${resolved}` }
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
try {
|
|
1630
|
+
process.chdir(resolved)
|
|
1631
|
+
return {
|
|
1632
|
+
content: [
|
|
1633
|
+
'── Directory Changed ──',
|
|
1634
|
+
'',
|
|
1635
|
+
`New CWD: ${resolved}`,
|
|
1636
|
+
'',
|
|
1637
|
+
'The AI will now work relative to this directory.',
|
|
1638
|
+
'Note: This changes the filesystem root for tools like Read, Write, Bash.',
|
|
1639
|
+
].join('\n'),
|
|
1640
|
+
}
|
|
1641
|
+
} catch (err) {
|
|
1642
|
+
return { content: `❌ Failed to change directory: ${(err as Error).message}` }
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
const hooksCmd: CommandHandler = async () => {
|
|
1647
|
+
const { existsSync, readdirSync } = await import('node:fs')
|
|
1648
|
+
const { join } = await import('node:path')
|
|
1649
|
+
const cwd = process.cwd()
|
|
1650
|
+
const hooksDir = join(cwd, '.mipham', 'hooks')
|
|
1651
|
+
|
|
1652
|
+
if (!existsSync(hooksDir)) {
|
|
1653
|
+
return {
|
|
1654
|
+
content: [
|
|
1655
|
+
'── Hooks ──',
|
|
1656
|
+
'',
|
|
1657
|
+
'No hooks configured.',
|
|
1658
|
+
'',
|
|
1659
|
+
'Create hook scripts in .mipham/hooks/:',
|
|
1660
|
+
' pre-tool-use.sh — runs before each tool',
|
|
1661
|
+
' post-tool-use.sh — runs after each tool',
|
|
1662
|
+
' stop.sh — runs when session ends',
|
|
1663
|
+
'',
|
|
1664
|
+
'Use /loop init to scaffold hooks directory.',
|
|
1665
|
+
].join('\n'),
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
try {
|
|
1670
|
+
const files = readdirSync(hooksDir).filter((f) => f.endsWith('.sh'))
|
|
1671
|
+
const lines: string[] = ['── Hooks ──', '', `Location: ${hooksDir}`, '']
|
|
1672
|
+
|
|
1673
|
+
for (const f of files) {
|
|
1674
|
+
try {
|
|
1675
|
+
const stat = (await import('node:fs')).statSync(join(hooksDir, f))
|
|
1676
|
+
const isExec = (stat.mode & 0o111) !== 0
|
|
1677
|
+
lines.push(
|
|
1678
|
+
` ${isExec ? '✅' : '⚠️'} ${f}${isExec ? '' : ' (not executable — run chmod +x)'}`,
|
|
1679
|
+
)
|
|
1680
|
+
} catch {
|
|
1681
|
+
lines.push(` 📄 ${f}`)
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
lines.push('')
|
|
1686
|
+
lines.push(`${files.length} hook(s) found.`)
|
|
1687
|
+
return { content: lines.join('\n') }
|
|
1688
|
+
} catch {
|
|
1689
|
+
return { content: '── Hooks ──\n\nCould not read hooks directory.' }
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
const batchCmd: CommandHandler = async () => {
|
|
1694
|
+
return {
|
|
1695
|
+
content: [
|
|
1696
|
+
'── Batch Operations ──',
|
|
1697
|
+
'',
|
|
1698
|
+
'Apply a change across multiple files or directories.',
|
|
1699
|
+
'',
|
|
1700
|
+
'Usage: type "apply this change to all .ts files in src/"',
|
|
1701
|
+
'',
|
|
1702
|
+
'The AI will:',
|
|
1703
|
+
' 1. Understand your change description',
|
|
1704
|
+
' 2. Find all matching files',
|
|
1705
|
+
' 3. Apply the change consistently',
|
|
1706
|
+
' 4. Report what was modified',
|
|
1707
|
+
'',
|
|
1708
|
+
'Use this for:',
|
|
1709
|
+
' • Renaming symbols across codebase',
|
|
1710
|
+
' • Updating import patterns',
|
|
1711
|
+
' • Applying consistent formatting changes',
|
|
1712
|
+
' • Bulk config updates',
|
|
1713
|
+
].join('\n'),
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1019
1717
|
// ═══════════════════════════════════════════════════════════════
|
|
1020
1718
|
// Export
|
|
1021
1719
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -2708,6 +3406,7 @@ const commandsListCmd: CommandHandler = () => {
|
|
|
2708
3406
|
History: [],
|
|
2709
3407
|
'Model & Provider': [],
|
|
2710
3408
|
'Tools & Skills': [],
|
|
3409
|
+
Plugins: [],
|
|
2711
3410
|
Workflow: [],
|
|
2712
3411
|
Project: [],
|
|
2713
3412
|
Environment: [],
|
|
@@ -2757,6 +3456,12 @@ const commandsListCmd: CommandHandler = () => {
|
|
|
2757
3456
|
'/install-skill': 'Tools & Skills',
|
|
2758
3457
|
'/remove-skill': 'Tools & Skills',
|
|
2759
3458
|
'/mcp': 'Tools & Skills',
|
|
3459
|
+
'/plugins': 'Plugins',
|
|
3460
|
+
'/browse-plugins': 'Plugins',
|
|
3461
|
+
'/install-plugin': 'Plugins',
|
|
3462
|
+
'/remove-plugin': 'Plugins',
|
|
3463
|
+
'/plugin-enable': 'Plugins',
|
|
3464
|
+
'/plugin-disable': 'Plugins',
|
|
2760
3465
|
'/commands': 'Tools & Skills',
|
|
2761
3466
|
'/plan': 'Workflow',
|
|
2762
3467
|
'/no-plan': 'Workflow',
|
|
@@ -2786,6 +3491,18 @@ const commandsListCmd: CommandHandler = () => {
|
|
|
2786
3491
|
'/bg': 'Agents',
|
|
2787
3492
|
'/artifact': 'Artifacts',
|
|
2788
3493
|
'/schedule': 'Other',
|
|
3494
|
+
'/commit': 'Workflow',
|
|
3495
|
+
'/push': 'Workflow',
|
|
3496
|
+
'/pr': 'Workflow',
|
|
3497
|
+
'/issue': 'Workflow',
|
|
3498
|
+
'/simplify': 'Workflow',
|
|
3499
|
+
'/lint': 'Workflow',
|
|
3500
|
+
'/files': 'Session & Identity',
|
|
3501
|
+
'/stats': 'Session & Identity',
|
|
3502
|
+
'/summary': 'Session & Identity',
|
|
3503
|
+
'/cd': 'Session & Identity',
|
|
3504
|
+
'/hooks': 'Workflow',
|
|
3505
|
+
'/batch': 'Workflow',
|
|
2789
3506
|
}
|
|
2790
3507
|
|
|
2791
3508
|
for (const name of names) {
|
|
@@ -2853,6 +3570,12 @@ registry.set('/reload-skills', reloadSkillsCmd)
|
|
|
2853
3570
|
registry.set('/browse-skills', browseSkillsCmd)
|
|
2854
3571
|
registry.set('/install-skill', installSkillCmd)
|
|
2855
3572
|
registry.set('/remove-skill', removeSkillCmd)
|
|
3573
|
+
registry.set('/plugins', pluginsCmd)
|
|
3574
|
+
registry.set('/browse-plugins', browsePluginsCmd)
|
|
3575
|
+
registry.set('/install-plugin', installPluginCmd)
|
|
3576
|
+
registry.set('/remove-plugin', removePluginCmd)
|
|
3577
|
+
registry.set('/plugin-enable', pluginEnableCmd)
|
|
3578
|
+
registry.set('/plugin-disable', pluginDisableCmd)
|
|
2856
3579
|
registry.set('/commands', commandsListCmd)
|
|
2857
3580
|
|
|
2858
3581
|
// Workflow
|
|
@@ -2904,6 +3627,24 @@ registry.set('/artifact', artifactBaseCmd)
|
|
|
2904
3627
|
registry.set('/branch', branchCmd)
|
|
2905
3628
|
registry.set('/schedule', scheduleCmd)
|
|
2906
3629
|
|
|
3630
|
+
// GitHub & Git Workflow (Claude Code parity)
|
|
3631
|
+
registry.set('/commit', commitCmd)
|
|
3632
|
+
registry.set('/push', pushCmd)
|
|
3633
|
+
registry.set('/pr', prCmd)
|
|
3634
|
+
registry.set('/issue', issueCmd)
|
|
3635
|
+
|
|
3636
|
+
// Code Quality (Claude Code parity)
|
|
3637
|
+
registry.set('/simplify', simplifyCmd)
|
|
3638
|
+
registry.set('/lint', lintCmd)
|
|
3639
|
+
|
|
3640
|
+
// Session Enhancement (Claude Code parity)
|
|
3641
|
+
registry.set('/files', filesCmd)
|
|
3642
|
+
registry.set('/stats', statsCmd)
|
|
3643
|
+
registry.set('/summary', summaryCmd)
|
|
3644
|
+
registry.set('/cd', cdCmd)
|
|
3645
|
+
registry.set('/hooks', hooksCmd)
|
|
3646
|
+
registry.set('/batch', batchCmd)
|
|
3647
|
+
|
|
2907
3648
|
// ═══════════════════════════════════════════════════════════════
|
|
2908
3649
|
// Public API
|
|
2909
3650
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -2961,6 +3702,12 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
2961
3702
|
'/install-skill': 'Install a skill by name or URL',
|
|
2962
3703
|
'/remove-skill': 'Remove an installed skill',
|
|
2963
3704
|
'/mcp': 'MCP server status',
|
|
3705
|
+
'/plugins': 'List installed plugins',
|
|
3706
|
+
'/browse-plugins': 'Browse community plugin marketplace',
|
|
3707
|
+
'/install-plugin': 'Install a plugin from npm or local path',
|
|
3708
|
+
'/remove-plugin': 'Remove an installed plugin',
|
|
3709
|
+
'/plugin-enable': 'Enable a disabled plugin',
|
|
3710
|
+
'/plugin-disable': 'Disable an enabled plugin',
|
|
2964
3711
|
'/plan': 'Enter plan mode',
|
|
2965
3712
|
'/no-plan': 'Exit plan mode',
|
|
2966
3713
|
'/tdd': 'TDD mode',
|
|
@@ -2990,6 +3737,18 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
2990
3737
|
'/bg': 'Run a background agent task',
|
|
2991
3738
|
'/artifact': 'Manage artifacts',
|
|
2992
3739
|
'/schedule': 'View scheduled tasks',
|
|
3740
|
+
'/commit': 'Generate commit message and review staged changes',
|
|
3741
|
+
'/push': 'Push the current branch',
|
|
3742
|
+
'/pr': 'Create a pull request',
|
|
3743
|
+
'/issue': 'File a GitHub issue',
|
|
3744
|
+
'/simplify': 'Review code for reuse, simplification, and quality',
|
|
3745
|
+
'/lint': 'Run linting on the project',
|
|
3746
|
+
'/files': 'List files in current working directory',
|
|
3747
|
+
'/stats': 'Show session usage statistics',
|
|
3748
|
+
'/summary': 'Generate session summary',
|
|
3749
|
+
'/cd': 'Change session working directory',
|
|
3750
|
+
'/hooks': 'Manage lifecycle hook scripts',
|
|
3751
|
+
'/batch': 'Apply changes across multiple files',
|
|
2993
3752
|
}
|
|
2994
3753
|
|
|
2995
3754
|
export function getCommandList(): CommandEntry[] {
|