ccjk 14.2.2 → 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +58 -341
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/detect.js +15 -0
- package/dist/commands/detect.js.map +1 -0
- package/dist/commands/doctor.js +68 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/git-install.js +50 -0
- package/dist/commands/git-install.js.map +1 -0
- package/dist/commands/init.js +102 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mcp.js +66 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/menu.js +33 -0
- package/dist/commands/menu.js.map +1 -0
- package/dist/core/detect.js +24 -0
- package/dist/core/detect.js.map +1 -0
- package/dist/core/lint.js +49 -0
- package/dist/core/lint.js.map +1 -0
- package/dist/core/mcp.js +41 -0
- package/dist/core/mcp.js.map +1 -0
- package/dist/core/paths.js +9 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/providers.js +53 -0
- package/dist/core/providers.js.map +1 -0
- package/dist/core/settings.js +31 -0
- package/dist/core/settings.js.map +1 -0
- package/dist/core/slash-templates.js +56 -0
- package/dist/core/slash-templates.js.map +1 -0
- package/dist/core/tools.js +27 -0
- package/dist/core/tools.js.map +1 -0
- package/package.json +43 -164
- package/README.HONEST.md +0 -176
- package/README.en.md +0 -67
- package/README.ja.md +0 -67
- package/README.ko.md +0 -67
- package/README.zh-CN.md +0 -86
- package/bin/ccjk.mjs +0 -5
- package/bin/ccjk.ts +0 -222
- package/dist/chunks/agent-teams.mjs +0 -145
- package/dist/chunks/agent.mjs +0 -1439
- package/dist/chunks/agents.mjs +0 -3783
- package/dist/chunks/api-cli.mjs +0 -135
- package/dist/chunks/api-config-selector.mjs +0 -159
- package/dist/chunks/api-providers.mjs +0 -144
- package/dist/chunks/api.mjs +0 -115
- package/dist/chunks/auto-bootstrap.mjs +0 -358
- package/dist/chunks/auto-fixer.mjs +0 -95
- package/dist/chunks/auto-updater.mjs +0 -507
- package/dist/chunks/banner.mjs +0 -173
- package/dist/chunks/bash.mjs +0 -187
- package/dist/chunks/boost.mjs +0 -474
- package/dist/chunks/brain-config.mjs +0 -75
- package/dist/chunks/brain-status.mjs +0 -89
- package/dist/chunks/ccjk-agents.mjs +0 -416
- package/dist/chunks/ccjk-all.mjs +0 -1046
- package/dist/chunks/ccjk-config.mjs +0 -445
- package/dist/chunks/ccjk-hooks.mjs +0 -1074
- package/dist/chunks/ccjk-mcp.mjs +0 -763
- package/dist/chunks/ccjk-setup.mjs +0 -765
- package/dist/chunks/ccjk-skills.mjs +0 -518
- package/dist/chunks/ccr.mjs +0 -109
- package/dist/chunks/ccu.mjs +0 -40
- package/dist/chunks/check-updates.mjs +0 -117
- package/dist/chunks/claude-code-incremental-manager.mjs +0 -761
- package/dist/chunks/claude-config.mjs +0 -606
- package/dist/chunks/claude-config2.mjs +0 -62
- package/dist/chunks/claude-wrapper.mjs +0 -85
- package/dist/chunks/clavue-config.mjs +0 -1454
- package/dist/chunks/cleanup-migration.mjs +0 -20
- package/dist/chunks/cli-hook.mjs +0 -4096
- package/dist/chunks/cloud-sync.mjs +0 -29
- package/dist/chunks/code-type-resolver.mjs +0 -880
- package/dist/chunks/codex-config-switch.mjs +0 -452
- package/dist/chunks/codex-provider-manager.mjs +0 -238
- package/dist/chunks/codex-uninstaller.mjs +0 -404
- package/dist/chunks/codex.mjs +0 -2141
- package/dist/chunks/commands.mjs +0 -108
- package/dist/chunks/commands2.mjs +0 -421
- package/dist/chunks/commit.mjs +0 -140
- package/dist/chunks/completion.mjs +0 -517
- package/dist/chunks/config-consolidator.mjs +0 -172
- package/dist/chunks/config-switch.mjs +0 -334
- package/dist/chunks/config.mjs +0 -558
- package/dist/chunks/config2.mjs +0 -484
- package/dist/chunks/config3.mjs +0 -486
- package/dist/chunks/constants.mjs +0 -323
- package/dist/chunks/context-opt.mjs +0 -444
- package/dist/chunks/context.mjs +0 -974
- package/dist/chunks/dashboard.mjs +0 -481
- package/dist/chunks/doctor.mjs +0 -1301
- package/dist/chunks/eval.mjs +0 -502
- package/dist/chunks/evolution.mjs +0 -322
- package/dist/chunks/features.mjs +0 -715
- package/dist/chunks/fish.mjs +0 -181
- package/dist/chunks/fs-operations.mjs +0 -180
- package/dist/chunks/health-alerts.mjs +0 -830
- package/dist/chunks/help.mjs +0 -341
- package/dist/chunks/hook-installer.mjs +0 -48
- package/dist/chunks/impact.mjs +0 -651
- package/dist/chunks/index.mjs +0 -23
- package/dist/chunks/index10.mjs +0 -19
- package/dist/chunks/index11.mjs +0 -1171
- package/dist/chunks/index12.mjs +0 -218
- package/dist/chunks/index13.mjs +0 -679
- package/dist/chunks/index14.mjs +0 -1009
- package/dist/chunks/index15.mjs +0 -194
- package/dist/chunks/index2.mjs +0 -7637
- package/dist/chunks/index3.mjs +0 -171
- package/dist/chunks/index4.mjs +0 -26
- package/dist/chunks/index5.mjs +0 -19
- package/dist/chunks/index6.mjs +0 -19092
- package/dist/chunks/index7.mjs +0 -616
- package/dist/chunks/index8.mjs +0 -1602
- package/dist/chunks/index9.mjs +0 -5384
- package/dist/chunks/init.mjs +0 -1911
- package/dist/chunks/installer.mjs +0 -757
- package/dist/chunks/installer2.mjs +0 -103
- package/dist/chunks/interview.mjs +0 -2927
- package/dist/chunks/json-config.mjs +0 -60
- package/dist/chunks/linux.mjs +0 -3863
- package/dist/chunks/macos.mjs +0 -69
- package/dist/chunks/main.mjs +0 -635
- package/dist/chunks/manager.mjs +0 -1048
- package/dist/chunks/marketplace.mjs +0 -265
- package/dist/chunks/mcp-cli.mjs +0 -205
- package/dist/chunks/mcp-performance.mjs +0 -187
- package/dist/chunks/mcp.mjs +0 -667
- package/dist/chunks/memory-check.mjs +0 -2973
- package/dist/chunks/memory-paths.mjs +0 -259
- package/dist/chunks/memory-sync.mjs +0 -209
- package/dist/chunks/memory.mjs +0 -354
- package/dist/chunks/metrics-display.mjs +0 -153
- package/dist/chunks/monitor.mjs +0 -1856
- package/dist/chunks/notification.mjs +0 -1864
- package/dist/chunks/onboarding.mjs +0 -386
- package/dist/chunks/package.mjs +0 -3
- package/dist/chunks/paradigm.mjs +0 -74
- package/dist/chunks/permission-manager.mjs +0 -250
- package/dist/chunks/permissions.mjs +0 -265
- package/dist/chunks/persistence-manager.mjs +0 -801
- package/dist/chunks/persistence.mjs +0 -707
- package/dist/chunks/platform.mjs +0 -395
- package/dist/chunks/plugin.mjs +0 -1936
- package/dist/chunks/powershell.mjs +0 -213
- package/dist/chunks/prompts.mjs +0 -244
- package/dist/chunks/providers.mjs +0 -263
- package/dist/chunks/quick-actions.mjs +0 -335
- package/dist/chunks/quick-provider.mjs +0 -755
- package/dist/chunks/quick-setup.mjs +0 -421
- package/dist/chunks/remote.mjs +0 -497
- package/dist/chunks/research.mjs +0 -1904
- package/dist/chunks/rollback.mjs +0 -38
- package/dist/chunks/session-manager.mjs +0 -1371
- package/dist/chunks/session.mjs +0 -878
- package/dist/chunks/sessions.mjs +0 -106
- package/dist/chunks/silent-updater.mjs +0 -396
- package/dist/chunks/simple-config.mjs +0 -122
- package/dist/chunks/skill.mjs +0 -117
- package/dist/chunks/skill2.mjs +0 -9052
- package/dist/chunks/skills-sync.mjs +0 -1343
- package/dist/chunks/skills.mjs +0 -577
- package/dist/chunks/slash-commands.mjs +0 -208
- package/dist/chunks/smart-guide.mjs +0 -247
- package/dist/chunks/snapshot.mjs +0 -58
- package/dist/chunks/startup.mjs +0 -487
- package/dist/chunks/stats.mjs +0 -191
- package/dist/chunks/status.mjs +0 -471
- package/dist/chunks/team.mjs +0 -63
- package/dist/chunks/thinking.mjs +0 -626
- package/dist/chunks/trace.mjs +0 -57
- package/dist/chunks/uninstall.mjs +0 -852
- package/dist/chunks/update.mjs +0 -174
- package/dist/chunks/upgrade-manager.mjs +0 -204
- package/dist/chunks/upgrade.mjs +0 -133
- package/dist/chunks/version-checker.mjs +0 -891
- package/dist/chunks/vim.mjs +0 -903
- package/dist/chunks/windows.mjs +0 -14
- package/dist/chunks/workflows.mjs +0 -633
- package/dist/chunks/wsl.mjs +0 -129
- package/dist/chunks/zero-config.mjs +0 -871
- package/dist/chunks/zsh.mjs +0 -182
- package/dist/cli.d.mts +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.mjs +0 -2684
- package/dist/i18n/locales/en/agent-teams.json +0 -18
- package/dist/i18n/locales/en/agentBrowser.json +0 -80
- package/dist/i18n/locales/en/agents.json +0 -135
- package/dist/i18n/locales/en/api.json +0 -63
- package/dist/i18n/locales/en/ccjk-agents.json +0 -33
- package/dist/i18n/locales/en/ccjk-all.json +0 -23
- package/dist/i18n/locales/en/ccjk-skills.json +0 -22
- package/dist/i18n/locales/en/ccjk.json +0 -276
- package/dist/i18n/locales/en/ccr.json +0 -65
- package/dist/i18n/locales/en/claude-md.json +0 -73
- package/dist/i18n/locales/en/cli.json +0 -148
- package/dist/i18n/locales/en/cloud-setup.json +0 -31
- package/dist/i18n/locales/en/cloud-sync.json +0 -147
- package/dist/i18n/locales/en/cloud.json +0 -40
- package/dist/i18n/locales/en/cloudPlugins.json +0 -118
- package/dist/i18n/locales/en/codex.json +0 -184
- package/dist/i18n/locales/en/cometix.json +0 -29
- package/dist/i18n/locales/en/common.json +0 -68
- package/dist/i18n/locales/en/config.json +0 -108
- package/dist/i18n/locales/en/configuration.json +0 -236
- package/dist/i18n/locales/en/context.json +0 -85
- package/dist/i18n/locales/en/dashboard.json +0 -78
- package/dist/i18n/locales/en/errors.json +0 -26
- package/dist/i18n/locales/en/evolution.json +0 -54
- package/dist/i18n/locales/en/hooks.json +0 -74
- package/dist/i18n/locales/en/hooksSync.json +0 -133
- package/dist/i18n/locales/en/installation.json +0 -83
- package/dist/i18n/locales/en/interview.json +0 -104
- package/dist/i18n/locales/en/language.json +0 -19
- package/dist/i18n/locales/en/lsp.json +0 -78
- package/dist/i18n/locales/en/marketplace.json +0 -116
- package/dist/i18n/locales/en/mcp.json +0 -180
- package/dist/i18n/locales/en/memory.json +0 -23
- package/dist/i18n/locales/en/menu.json +0 -299
- package/dist/i18n/locales/en/multi-config.json +0 -79
- package/dist/i18n/locales/en/notification.json +0 -307
- package/dist/i18n/locales/en/permissions.json +0 -95
- package/dist/i18n/locales/en/persistence.json +0 -127
- package/dist/i18n/locales/en/plugins.json +0 -146
- package/dist/i18n/locales/en/quick-actions.json +0 -78
- package/dist/i18n/locales/en/registry.json +0 -54
- package/dist/i18n/locales/en/remote.json +0 -93
- package/dist/i18n/locales/en/sandbox.json +0 -44
- package/dist/i18n/locales/en/setup.json +0 -44
- package/dist/i18n/locales/en/shencha.json +0 -14
- package/dist/i18n/locales/en/skills.json +0 -100
- package/dist/i18n/locales/en/skillsSync.json +0 -74
- package/dist/i18n/locales/en/smartGuide.json +0 -49
- package/dist/i18n/locales/en/stats.json +0 -20
- package/dist/i18n/locales/en/subagent.json +0 -69
- package/dist/i18n/locales/en/superpowers.json +0 -117
- package/dist/i18n/locales/en/team.json +0 -7
- package/dist/i18n/locales/en/thinking.json +0 -65
- package/dist/i18n/locales/en/tools.json +0 -42
- package/dist/i18n/locales/en/uninstall.json +0 -56
- package/dist/i18n/locales/en/updater.json +0 -29
- package/dist/i18n/locales/en/vim.json +0 -169
- package/dist/i18n/locales/en/workflow.json +0 -55
- package/dist/i18n/locales/en/workspace.json +0 -108
- package/dist/i18n/locales/zh-CN/agent-teams.json +0 -18
- package/dist/i18n/locales/zh-CN/agentBrowser.json +0 -80
- package/dist/i18n/locales/zh-CN/agents.json +0 -135
- package/dist/i18n/locales/zh-CN/api.json +0 -63
- package/dist/i18n/locales/zh-CN/ccjk-agents.json +0 -33
- package/dist/i18n/locales/zh-CN/ccjk-all.json +0 -23
- package/dist/i18n/locales/zh-CN/ccjk-skills.json +0 -22
- package/dist/i18n/locales/zh-CN/ccjk.json +0 -276
- package/dist/i18n/locales/zh-CN/ccr.json +0 -65
- package/dist/i18n/locales/zh-CN/claude-md.json +0 -73
- package/dist/i18n/locales/zh-CN/cli.json +0 -148
- package/dist/i18n/locales/zh-CN/cloud-setup.json +0 -31
- package/dist/i18n/locales/zh-CN/cloud-sync.json +0 -147
- package/dist/i18n/locales/zh-CN/cloud.json +0 -40
- package/dist/i18n/locales/zh-CN/cloudPlugins.json +0 -118
- package/dist/i18n/locales/zh-CN/codex.json +0 -184
- package/dist/i18n/locales/zh-CN/cometix.json +0 -29
- package/dist/i18n/locales/zh-CN/common.json +0 -68
- package/dist/i18n/locales/zh-CN/config.json +0 -108
- package/dist/i18n/locales/zh-CN/configuration.json +0 -234
- package/dist/i18n/locales/zh-CN/context.json +0 -85
- package/dist/i18n/locales/zh-CN/dashboard.json +0 -78
- package/dist/i18n/locales/zh-CN/errors.json +0 -26
- package/dist/i18n/locales/zh-CN/evolution.json +0 -54
- package/dist/i18n/locales/zh-CN/hooks.json +0 -74
- package/dist/i18n/locales/zh-CN/hooksSync.json +0 -133
- package/dist/i18n/locales/zh-CN/installation.json +0 -83
- package/dist/i18n/locales/zh-CN/interview.json +0 -104
- package/dist/i18n/locales/zh-CN/language.json +0 -19
- package/dist/i18n/locales/zh-CN/lsp.json +0 -78
- package/dist/i18n/locales/zh-CN/marketplace.json +0 -116
- package/dist/i18n/locales/zh-CN/mcp.json +0 -180
- package/dist/i18n/locales/zh-CN/memory.json +0 -23
- package/dist/i18n/locales/zh-CN/menu.json +0 -299
- package/dist/i18n/locales/zh-CN/multi-config.json +0 -79
- package/dist/i18n/locales/zh-CN/notification.json +0 -307
- package/dist/i18n/locales/zh-CN/permissions.json +0 -95
- package/dist/i18n/locales/zh-CN/persistence.json +0 -127
- package/dist/i18n/locales/zh-CN/plugins.json +0 -146
- package/dist/i18n/locales/zh-CN/quick-actions.json +0 -78
- package/dist/i18n/locales/zh-CN/registry.json +0 -54
- package/dist/i18n/locales/zh-CN/remote.json +0 -93
- package/dist/i18n/locales/zh-CN/sandbox.json +0 -44
- package/dist/i18n/locales/zh-CN/setup.json +0 -44
- package/dist/i18n/locales/zh-CN/shencha.json +0 -14
- package/dist/i18n/locales/zh-CN/skills.json +0 -100
- package/dist/i18n/locales/zh-CN/skillsSync.json +0 -74
- package/dist/i18n/locales/zh-CN/smartGuide.json +0 -49
- package/dist/i18n/locales/zh-CN/stats.json +0 -20
- package/dist/i18n/locales/zh-CN/subagent.json +0 -69
- package/dist/i18n/locales/zh-CN/superpowers.json +0 -117
- package/dist/i18n/locales/zh-CN/team.json +0 -7
- package/dist/i18n/locales/zh-CN/thinking.json +0 -65
- package/dist/i18n/locales/zh-CN/tools.json +0 -42
- package/dist/i18n/locales/zh-CN/uninstall.json +0 -56
- package/dist/i18n/locales/zh-CN/updater.json +0 -29
- package/dist/i18n/locales/zh-CN/vim.json +0 -169
- package/dist/i18n/locales/zh-CN/workflow.json +0 -55
- package/dist/i18n/locales/zh-CN/workspace.json +0 -108
- package/dist/index.d.mts +0 -5658
- package/dist/index.d.ts +0 -5658
- package/dist/index.mjs +0 -3732
- package/dist/shared/ccjk.5bEolFrk.mjs +0 -254
- package/dist/shared/ccjk.8oaxX4iR.mjs +0 -90
- package/dist/shared/ccjk.B2U7DsPy.mjs +0 -31
- package/dist/shared/ccjk.B2f-cwUP.mjs +0 -468
- package/dist/shared/ccjk.BAGoDD49.mjs +0 -36
- package/dist/shared/ccjk.BBtCGd_g.mjs +0 -899
- package/dist/shared/ccjk.BFQ7yr5S.mjs +0 -16
- package/dist/shared/ccjk.BLsIiTqO.mjs +0 -449
- package/dist/shared/ccjk.BXv8aYs1.mjs +0 -170
- package/dist/shared/ccjk.BnsY5WxD.mjs +0 -171
- package/dist/shared/ccjk.BoApaI4j.mjs +0 -28
- package/dist/shared/ccjk.Bq8TqZG_.mjs +0 -189
- package/dist/shared/ccjk.BtrioX1Z.mjs +0 -25
- package/dist/shared/ccjk.Bx_rmYfN.mjs +0 -69
- package/dist/shared/ccjk.BzPbSEP2.mjs +0 -115
- package/dist/shared/ccjk.C0WLUnFV.mjs +0 -293
- package/dist/shared/ccjk.C1hANZTu.mjs +0 -19
- package/dist/shared/ccjk.C2jHOZVP.mjs +0 -52
- package/dist/shared/ccjk.CNhnT6uQ.mjs +0 -636
- package/dist/shared/ccjk.COweQ1RR.mjs +0 -5
- package/dist/shared/ccjk.CfKKcvWy.mjs +0 -126
- package/dist/shared/ccjk.Cjgrln_h.mjs +0 -297
- package/dist/shared/ccjk.CoCHVXl3.mjs +0 -3951
- package/dist/shared/ccjk.CwGZSTAK.mjs +0 -319
- package/dist/shared/ccjk.CxpGa6MC.mjs +0 -2724
- package/dist/shared/ccjk.D-magaEx.mjs +0 -763
- package/dist/shared/ccjk.D0g2ABGg.mjs +0 -171
- package/dist/shared/ccjk.D6ycHbak.mjs +0 -270
- package/dist/shared/ccjk.D75wivnp.mjs +0 -142
- package/dist/shared/ccjk.DDL-4C-k.mjs +0 -100
- package/dist/shared/ccjk.DFRPtmK_.mjs +0 -75
- package/dist/shared/ccjk.DMV3x5Sd.mjs +0 -299
- package/dist/shared/ccjk.DZ2LLOa-.mjs +0 -2195
- package/dist/shared/ccjk.DbigonEQ.mjs +0 -698
- package/dist/shared/ccjk.DcMvE7lf.mjs +0 -618
- package/dist/shared/ccjk.DeWpAShp.mjs +0 -1828
- package/dist/shared/ccjk.DhJ1kyDR.mjs +0 -30
- package/dist/shared/ccjk.DlTXS9rP.mjs +0 -224
- package/dist/shared/ccjk.DopKzo3z.mjs +0 -305
- package/dist/shared/ccjk.DsZsc4LR.mjs +0 -1280
- package/dist/shared/ccjk.DuzJZlgj.mjs +0 -418
- package/dist/shared/ccjk.Dxgd2vjc.mjs +0 -444
- package/dist/shared/ccjk.J8YiPsOw.mjs +0 -259
- package/dist/shared/ccjk.KfSWcGlE.mjs +0 -38
- package/dist/shared/ccjk.L7yC58_i.mjs +0 -225
- package/dist/shared/ccjk.MwtjAULc.mjs +0 -1447
- package/dist/shared/ccjk.OJKHVSOb.mjs +0 -2005
- package/dist/shared/ccjk.OTnevPNE.mjs +0 -225
- package/dist/shared/ccjk.RyizuzOI.mjs +0 -21
- package/dist/shared/ccjk.T_cX87dY.mjs +0 -15
- package/dist/shared/ccjk.bQ7Dh1g4.mjs +0 -249
- package/dist/shared/ccjk.gDEDGD_t.mjs +0 -38
- package/dist/shared/ccjk.hoqrwWdN.mjs +0 -333
- package/dist/shared/ccjk.i_vn-9C3.mjs +0 -317
- package/dist/shared/ccjk.lG3ccFjm.mjs +0 -885
- package/dist/shared/ccjk.wLJHO0Af.mjs +0 -244
- package/dist/shared/ccjk.y-a_1vK4.mjs +0 -5127
- package/dist/templates/agents/README.md +0 -78
- package/dist/templates/agents/fullstack-developer.json +0 -70
- package/dist/templates/agents/go-expert.json +0 -69
- package/dist/templates/agents/index.json +0 -64
- package/dist/templates/agents/python-expert.json +0 -69
- package/dist/templates/agents/react-specialist.json +0 -69
- package/dist/templates/agents/testing-automation-expert.json +0 -70
- package/dist/templates/agents/typescript-architect.json +0 -69
- package/dist/templates/claude-code/common/settings.json +0 -109
- package/dist/templates/common/error-prevention.md +0 -267
- package/dist/templates/common/karpathy-baseline.md +0 -83
- package/dist/templates/common/output-styles/zh-CN/carmack-mode.md +0 -381
- package/dist/templates/common/output-styles/zh-CN/codex-rigor-mode.md +0 -114
- package/dist/templates/common/output-styles/zh-CN/dhh-mode.md +0 -265
- package/dist/templates/common/output-styles/zh-CN/evan-you-mode.md +0 -539
- package/dist/templates/common/output-styles/zh-CN/jobs-mode.md +0 -369
- package/dist/templates/common/output-styles/zh-CN/linus-mode.md +0 -135
- package/dist/templates/common/output-styles/zh-CN/uncle-bob-mode.md +0 -221
- package/dist/templates/common/workflow/continuousDelivery/en/continuous-delivery.md +0 -628
- package/dist/templates/common/workflow/continuousDelivery/zh-CN/continuous-delivery.md +0 -628
- package/dist/templates/common/workflow/essential/en/agents/ccjk-config-agent.md +0 -187
- package/dist/templates/common/workflow/essential/en/agents/ccjk-mcp-agent.md +0 -191
- package/dist/templates/common/workflow/essential/en/agents/ccjk-skill-agent.md +0 -249
- package/dist/templates/common/workflow/essential/en/agents/ccjk-workflow-agent.md +0 -277
- package/dist/templates/common/workflow/essential/en/agents/get-current-datetime.md +0 -29
- package/dist/templates/common/workflow/essential/en/agents/init-architect.md +0 -115
- package/dist/templates/common/workflow/essential/en/agents/ui-ux-designer.md +0 -91
- package/dist/templates/common/workflow/essential/en/feat.md +0 -92
- package/dist/templates/common/workflow/essential/en/goal.md +0 -147
- package/dist/templates/common/workflow/essential/en/init-project.md +0 -53
- package/dist/templates/common/workflow/essential/zh-CN/agents/get-current-datetime.md +0 -29
- package/dist/templates/common/workflow/essential/zh-CN/agents/init-architect.md +0 -115
- package/dist/templates/common/workflow/essential/zh-CN/agents/ui-ux-designer.md +0 -91
- package/dist/templates/common/workflow/essential/zh-CN/feat.md +0 -315
- package/dist/templates/common/workflow/essential/zh-CN/goal.md +0 -146
- package/dist/templates/common/workflow/essential/zh-CN/init-project.md +0 -53
- package/dist/templates/common/workflow/git/en/git-cleanBranches.md +0 -102
- package/dist/templates/common/workflow/git/en/git-commit.md +0 -205
- package/dist/templates/common/workflow/git/en/git-rollback.md +0 -90
- package/dist/templates/common/workflow/git/en/git-worktree.md +0 -276
- package/dist/templates/common/workflow/git/zh-CN/git-cleanBranches.md +0 -102
- package/dist/templates/common/workflow/git/zh-CN/git-commit.md +0 -205
- package/dist/templates/common/workflow/git/zh-CN/git-rollback.md +0 -90
- package/dist/templates/common/workflow/git/zh-CN/git-worktree.md +0 -276
- package/dist/templates/common/workflow/interview/en/interview.md +0 -67
- package/dist/templates/common/workflow/interview/zh-CN/interview.md +0 -67
- package/dist/templates/common/workflow/linearMethod/en/linear-method.md +0 -651
- package/dist/templates/common/workflow/linearMethod/zh-CN/linear-method.md +0 -752
- package/dist/templates/common/workflow/refactoringMaster/en/refactoring-master.md +0 -516
- package/dist/templates/common/workflow/refactoringMaster/zh-CN/refactoring-master.md +0 -812
- package/dist/templates/common/workflow/sixStep/en/workflow.md +0 -83
- package/dist/templates/common/workflow/sixStep/zh-CN/workflow.md +0 -359
- package/dist/templates/common/workflow/specFirstTDD/en/spec-first-tdd.md +0 -364
- package/dist/templates/common/workflow/specFirstTDD/zh-CN/spec-first-tdd.md +0 -366
- package/dist/templates/hooks/README.md +0 -212
- package/dist/templates/hooks/git-workflow-hooks.md +0 -551
- package/dist/templates/hooks/post-test/coverage.json +0 -21
- package/dist/templates/hooks/post-test/summary.json +0 -21
- package/dist/templates/hooks/post-test-coverage.md +0 -434
- package/dist/templates/hooks/pre-commit/eslint.json +0 -22
- package/dist/templates/hooks/pre-commit/prettier.json +0 -22
- package/dist/templates/hooks/pre-commit-black.md +0 -274
- package/dist/templates/hooks/pre-commit-eslint.md +0 -153
- package/dist/templates/hooks/pre-commit-gofmt.md +0 -284
- package/dist/templates/hooks/pre-commit-prettier.md +0 -212
- package/dist/templates/hooks/pre-commit-type-check.md +0 -377
- package/dist/templates/skills/ccjk-init.md +0 -154
- package/dist/templates/skills/ccjk-mcp-setup.md +0 -205
- package/dist/templates/skills/ccjk-troubleshoot.md +0 -228
- package/dist/templates/skills/django-patterns.md +0 -1016
- package/dist/templates/skills/git-workflow.md +0 -748
- package/dist/templates/skills/go-idioms.md +0 -963
- package/dist/templates/skills/index.json +0 -132
- package/dist/templates/skills/nextjs-optimization.md +0 -694
- package/dist/templates/skills/python-pep8.md +0 -852
- package/dist/templates/skills/react-patterns.md +0 -686
- package/dist/templates/skills/rust-patterns.md +0 -1057
- package/dist/templates/skills/security-best-practices.md +0 -1413
- package/dist/templates/skills/testing-best-practices.md +0 -1315
- package/dist/templates/skills/ts-best-practices.md +0 -354
- package/templates/agents/README.md +0 -78
- package/templates/agents/fullstack-developer.json +0 -70
- package/templates/agents/go-expert.json +0 -69
- package/templates/agents/index.json +0 -64
- package/templates/agents/python-expert.json +0 -69
- package/templates/agents/react-specialist.json +0 -69
- package/templates/agents/testing-automation-expert.json +0 -70
- package/templates/agents/typescript-architect.json +0 -69
- package/templates/claude-code/common/settings.json +0 -109
- package/templates/common/error-prevention.md +0 -267
- package/templates/common/karpathy-baseline.md +0 -83
- package/templates/common/output-styles/zh-CN/carmack-mode.md +0 -381
- package/templates/common/output-styles/zh-CN/codex-rigor-mode.md +0 -114
- package/templates/common/output-styles/zh-CN/dhh-mode.md +0 -265
- package/templates/common/output-styles/zh-CN/evan-you-mode.md +0 -539
- package/templates/common/output-styles/zh-CN/jobs-mode.md +0 -369
- package/templates/common/output-styles/zh-CN/linus-mode.md +0 -135
- package/templates/common/output-styles/zh-CN/uncle-bob-mode.md +0 -221
- package/templates/common/workflow/continuousDelivery/en/continuous-delivery.md +0 -628
- package/templates/common/workflow/continuousDelivery/zh-CN/continuous-delivery.md +0 -628
- package/templates/common/workflow/essential/en/agents/ccjk-config-agent.md +0 -187
- package/templates/common/workflow/essential/en/agents/ccjk-mcp-agent.md +0 -191
- package/templates/common/workflow/essential/en/agents/ccjk-skill-agent.md +0 -249
- package/templates/common/workflow/essential/en/agents/ccjk-workflow-agent.md +0 -277
- package/templates/common/workflow/essential/en/agents/get-current-datetime.md +0 -29
- package/templates/common/workflow/essential/en/agents/init-architect.md +0 -115
- package/templates/common/workflow/essential/en/agents/ui-ux-designer.md +0 -91
- package/templates/common/workflow/essential/en/feat.md +0 -92
- package/templates/common/workflow/essential/en/goal.md +0 -147
- package/templates/common/workflow/essential/en/init-project.md +0 -53
- package/templates/common/workflow/essential/zh-CN/agents/get-current-datetime.md +0 -29
- package/templates/common/workflow/essential/zh-CN/agents/init-architect.md +0 -115
- package/templates/common/workflow/essential/zh-CN/agents/ui-ux-designer.md +0 -91
- package/templates/common/workflow/essential/zh-CN/feat.md +0 -315
- package/templates/common/workflow/essential/zh-CN/goal.md +0 -146
- package/templates/common/workflow/essential/zh-CN/init-project.md +0 -53
- package/templates/common/workflow/git/en/git-cleanBranches.md +0 -102
- package/templates/common/workflow/git/en/git-commit.md +0 -205
- package/templates/common/workflow/git/en/git-rollback.md +0 -90
- package/templates/common/workflow/git/en/git-worktree.md +0 -276
- package/templates/common/workflow/git/zh-CN/git-cleanBranches.md +0 -102
- package/templates/common/workflow/git/zh-CN/git-commit.md +0 -205
- package/templates/common/workflow/git/zh-CN/git-rollback.md +0 -90
- package/templates/common/workflow/git/zh-CN/git-worktree.md +0 -276
- package/templates/common/workflow/interview/en/interview.md +0 -67
- package/templates/common/workflow/interview/zh-CN/interview.md +0 -67
- package/templates/common/workflow/linearMethod/en/linear-method.md +0 -651
- package/templates/common/workflow/linearMethod/zh-CN/linear-method.md +0 -752
- package/templates/common/workflow/refactoringMaster/en/refactoring-master.md +0 -516
- package/templates/common/workflow/refactoringMaster/zh-CN/refactoring-master.md +0 -812
- package/templates/common/workflow/sixStep/en/workflow.md +0 -83
- package/templates/common/workflow/sixStep/zh-CN/workflow.md +0 -359
- package/templates/common/workflow/specFirstTDD/en/spec-first-tdd.md +0 -364
- package/templates/common/workflow/specFirstTDD/zh-CN/spec-first-tdd.md +0 -366
- package/templates/hooks/README.md +0 -212
- package/templates/hooks/git-workflow-hooks.md +0 -551
- package/templates/hooks/post-test/coverage.json +0 -21
- package/templates/hooks/post-test/summary.json +0 -21
- package/templates/hooks/post-test-coverage.md +0 -434
- package/templates/hooks/pre-commit/eslint.json +0 -22
- package/templates/hooks/pre-commit/prettier.json +0 -22
- package/templates/hooks/pre-commit-black.md +0 -274
- package/templates/hooks/pre-commit-eslint.md +0 -153
- package/templates/hooks/pre-commit-gofmt.md +0 -284
- package/templates/hooks/pre-commit-prettier.md +0 -212
- package/templates/hooks/pre-commit-type-check.md +0 -377
- package/templates/skills/basic.hbs +0 -72
- package/templates/skills/ccjk-init.md +0 -154
- package/templates/skills/ccjk-mcp-setup.md +0 -205
- package/templates/skills/ccjk-troubleshoot.md +0 -228
- package/templates/skills/code-refactor.hbs +0 -133
- package/templates/skills/code-review.hbs +0 -141
- package/templates/skills/django-patterns.md +0 -1016
- package/templates/skills/git-workflow.md +0 -748
- package/templates/skills/go-idioms.md +0 -963
- package/templates/skills/index.json +0 -132
- package/templates/skills/nextjs-optimization.md +0 -694
- package/templates/skills/python-pep8.md +0 -852
- package/templates/skills/react-patterns.md +0 -686
- package/templates/skills/rust-patterns.md +0 -1057
- package/templates/skills/security-best-practices.md +0 -1413
- package/templates/skills/testing-best-practices.md +0 -1315
- package/templates/skills/ts-best-practices.md +0 -354
- package/templates/skills/type-fix.hbs +0 -132
|
@@ -1,1864 +0,0 @@
|
|
|
1
|
-
import a from './index5.mjs';
|
|
2
|
-
import { i as inquirer } from './index6.mjs';
|
|
3
|
-
import { i18n } from './index2.mjs';
|
|
4
|
-
import { m as maskToken, a as isValidTokenFormat, c as generateDeviceToken, d as decryptToken, e as encryptToken, f as getDeviceInfo, i as isDeviceBound, g as getBindingStatus, u as unbindDevice, b as bindDevice, s as sendNotification } from '../shared/ccjk.DcMvE7lf.mjs';
|
|
5
|
-
import { exec } from 'node:child_process';
|
|
6
|
-
import * as fs from 'node:fs';
|
|
7
|
-
import fs__default from 'node:fs';
|
|
8
|
-
import * as os from 'node:os';
|
|
9
|
-
import os__default from 'node:os';
|
|
10
|
-
import * as path from 'node:path';
|
|
11
|
-
import path__default from 'node:path';
|
|
12
|
-
import process__default from 'node:process';
|
|
13
|
-
import { promisify } from 'node:util';
|
|
14
|
-
import { writeFileAtomic } from './fs-operations.mjs';
|
|
15
|
-
import { p as parse } from '../shared/ccjk.BBtCGd_g.mjs';
|
|
16
|
-
import { stringify } from './index3.mjs';
|
|
17
|
-
import { CLOUD_ENDPOINTS } from './constants.mjs';
|
|
18
|
-
import '../shared/ccjk.BAGoDD49.mjs';
|
|
19
|
-
import 'node:readline';
|
|
20
|
-
import 'stream';
|
|
21
|
-
import 'node:tty';
|
|
22
|
-
import 'node:async_hooks';
|
|
23
|
-
import '../shared/ccjk.Cjgrln_h.mjs';
|
|
24
|
-
import 'tty';
|
|
25
|
-
import 'fs';
|
|
26
|
-
import 'child_process';
|
|
27
|
-
import 'node:crypto';
|
|
28
|
-
import 'buffer';
|
|
29
|
-
import 'string_decoder';
|
|
30
|
-
import 'node:url';
|
|
31
|
-
import '../shared/ccjk.bQ7Dh1g4.mjs';
|
|
32
|
-
import '../shared/ccjk.D0g2ABGg.mjs';
|
|
33
|
-
import '../shared/ccjk.D6ycHbak.mjs';
|
|
34
|
-
import 'node:buffer';
|
|
35
|
-
import 'node:fs/promises';
|
|
36
|
-
|
|
37
|
-
const execAsync = promisify(exec);
|
|
38
|
-
const DEFAULT_CONFIG = {
|
|
39
|
-
shortcutName: "ClaudeNotify",
|
|
40
|
-
barkUrl: "",
|
|
41
|
-
preferLocal: true,
|
|
42
|
-
smartNotify: true,
|
|
43
|
-
fallbackToBark: true
|
|
44
|
-
};
|
|
45
|
-
const CONFIG_DIR = path.join(os.homedir(), ".ccjk");
|
|
46
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, "notification-config.json");
|
|
47
|
-
const TEMP_NOTIFICATION_FILE = "/tmp/ccjk-notification.json";
|
|
48
|
-
class LocalNotificationService {
|
|
49
|
-
config;
|
|
50
|
-
constructor(config) {
|
|
51
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
52
|
-
}
|
|
53
|
-
// ==========================================================================
|
|
54
|
-
// Screen Lock Detection
|
|
55
|
-
// ==========================================================================
|
|
56
|
-
/**
|
|
57
|
-
* Check if the macOS screen is locked
|
|
58
|
-
*
|
|
59
|
-
* Uses Python with Quartz framework to detect screen lock status.
|
|
60
|
-
*
|
|
61
|
-
* @returns true if screen is locked, false otherwise
|
|
62
|
-
*/
|
|
63
|
-
async isScreenLocked() {
|
|
64
|
-
if (process__default.platform !== "darwin") {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
try {
|
|
68
|
-
const pythonScript = `
|
|
69
|
-
import Quartz
|
|
70
|
-
session_dict = Quartz.CGSessionCopyCurrentDictionary()
|
|
71
|
-
if session_dict:
|
|
72
|
-
locked = session_dict.get('CGSSessionScreenIsLocked', False)
|
|
73
|
-
print('true' if locked else 'false')
|
|
74
|
-
else:
|
|
75
|
-
print('false')
|
|
76
|
-
`;
|
|
77
|
-
const { stdout } = await execAsync(`python3 -c "${pythonScript}"`, {
|
|
78
|
-
timeout: 5e3
|
|
79
|
-
});
|
|
80
|
-
return stdout.trim().toLowerCase() === "true";
|
|
81
|
-
} catch (error) {
|
|
82
|
-
console.error("Failed to detect screen lock status:", error);
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// ==========================================================================
|
|
87
|
-
// macOS Shortcut Notification
|
|
88
|
-
// ==========================================================================
|
|
89
|
-
/**
|
|
90
|
-
* Send notification via macOS Shortcuts
|
|
91
|
-
*
|
|
92
|
-
* Creates a JSON file with notification data and runs the specified shortcut.
|
|
93
|
-
* The shortcut should be configured to read the JSON and display a notification.
|
|
94
|
-
*
|
|
95
|
-
* @param shortcutName - Name of the macOS Shortcut to run
|
|
96
|
-
* @param options - Notification options
|
|
97
|
-
*/
|
|
98
|
-
async sendShortcutNotification(shortcutName, options) {
|
|
99
|
-
if (process__default.platform !== "darwin") {
|
|
100
|
-
throw new Error("macOS Shortcuts are only available on macOS");
|
|
101
|
-
}
|
|
102
|
-
const notificationData = {
|
|
103
|
-
title: options.title,
|
|
104
|
-
body: options.body,
|
|
105
|
-
sound: options.sound !== false,
|
|
106
|
-
url: options.url || "",
|
|
107
|
-
group: options.group || "ccjk",
|
|
108
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
109
|
-
};
|
|
110
|
-
writeFileAtomic(TEMP_NOTIFICATION_FILE, JSON.stringify(notificationData, null, 2), "utf-8");
|
|
111
|
-
try {
|
|
112
|
-
await execAsync(`shortcuts run "${shortcutName}" --input-path "${TEMP_NOTIFICATION_FILE}"`, {
|
|
113
|
-
timeout: 3e4
|
|
114
|
-
// 30 second timeout
|
|
115
|
-
});
|
|
116
|
-
} finally {
|
|
117
|
-
try {
|
|
118
|
-
if (fs.existsSync(TEMP_NOTIFICATION_FILE)) {
|
|
119
|
-
fs.unlinkSync(TEMP_NOTIFICATION_FILE);
|
|
120
|
-
}
|
|
121
|
-
} catch {
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// ==========================================================================
|
|
126
|
-
// Bark Push Notification
|
|
127
|
-
// ==========================================================================
|
|
128
|
-
/**
|
|
129
|
-
* Send notification via Bark push service
|
|
130
|
-
*
|
|
131
|
-
* Bark is an iOS app that allows sending push notifications via HTTP API.
|
|
132
|
-
* API format: https://api.day.app/YOUR_KEY/title/body
|
|
133
|
-
*
|
|
134
|
-
* @param barkUrl - Bark API URL (e.g., https://api.day.app/YOUR_KEY)
|
|
135
|
-
* @param options - Notification options
|
|
136
|
-
*/
|
|
137
|
-
async sendBarkNotification(barkUrl, options) {
|
|
138
|
-
if (!barkUrl) {
|
|
139
|
-
throw new Error("Bark URL is not configured");
|
|
140
|
-
}
|
|
141
|
-
const encodedTitle = encodeURIComponent(options.title);
|
|
142
|
-
const encodedBody = encodeURIComponent(options.body);
|
|
143
|
-
const params = new URLSearchParams();
|
|
144
|
-
if (options.sound !== false) {
|
|
145
|
-
params.append("sound", "default");
|
|
146
|
-
}
|
|
147
|
-
if (options.url) {
|
|
148
|
-
params.append("url", options.url);
|
|
149
|
-
}
|
|
150
|
-
if (options.group) {
|
|
151
|
-
params.append("group", options.group);
|
|
152
|
-
}
|
|
153
|
-
if (options.icon) {
|
|
154
|
-
params.append("icon", options.icon);
|
|
155
|
-
}
|
|
156
|
-
const baseUrl = barkUrl.endsWith("/") ? barkUrl.slice(0, -1) : barkUrl;
|
|
157
|
-
let fullUrl = `${baseUrl}/${encodedTitle}/${encodedBody}`;
|
|
158
|
-
const queryString = params.toString();
|
|
159
|
-
if (queryString) {
|
|
160
|
-
fullUrl += `?${queryString}`;
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
await execAsync(`curl -s -o /dev/null -w "%{http_code}" "${fullUrl}"`, {
|
|
164
|
-
timeout: 1e4
|
|
165
|
-
});
|
|
166
|
-
} catch (error) {
|
|
167
|
-
throw new Error(`Failed to send Bark notification: ${error}`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// ==========================================================================
|
|
171
|
-
// Smart Notification
|
|
172
|
-
// ==========================================================================
|
|
173
|
-
/**
|
|
174
|
-
* Smart notification - automatically choose notification method
|
|
175
|
-
*
|
|
176
|
-
* Logic:
|
|
177
|
-
* 1. If screen is unlocked and preferLocal is true, use macOS Shortcut
|
|
178
|
-
* 2. If screen is locked or shortcut fails, use Bark
|
|
179
|
-
* 3. If both fail, throw error
|
|
180
|
-
*
|
|
181
|
-
* @param options - Notification options
|
|
182
|
-
* @returns Notification result
|
|
183
|
-
*/
|
|
184
|
-
async smartNotify(options) {
|
|
185
|
-
const isLocked = await this.isScreenLocked();
|
|
186
|
-
if (!isLocked && this.config.preferLocal && this.config.shortcutName) {
|
|
187
|
-
try {
|
|
188
|
-
await this.sendShortcutNotification(this.config.shortcutName, options);
|
|
189
|
-
return {
|
|
190
|
-
success: true,
|
|
191
|
-
method: "shortcut"
|
|
192
|
-
};
|
|
193
|
-
} catch (error) {
|
|
194
|
-
if (this.config.fallbackToBark && this.config.barkUrl) {
|
|
195
|
-
try {
|
|
196
|
-
await this.sendBarkNotification(this.config.barkUrl, options);
|
|
197
|
-
return {
|
|
198
|
-
success: true,
|
|
199
|
-
method: "bark"
|
|
200
|
-
};
|
|
201
|
-
} catch (barkError) {
|
|
202
|
-
return {
|
|
203
|
-
success: false,
|
|
204
|
-
method: "none",
|
|
205
|
-
error: `Shortcut failed: ${error}. Bark also failed: ${barkError}`
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return {
|
|
210
|
-
success: false,
|
|
211
|
-
method: "none",
|
|
212
|
-
error: `Shortcut notification failed: ${error}`
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (this.config.barkUrl) {
|
|
217
|
-
try {
|
|
218
|
-
await this.sendBarkNotification(this.config.barkUrl, options);
|
|
219
|
-
return {
|
|
220
|
-
success: true,
|
|
221
|
-
method: "bark"
|
|
222
|
-
};
|
|
223
|
-
} catch (error) {
|
|
224
|
-
return {
|
|
225
|
-
success: false,
|
|
226
|
-
method: "none",
|
|
227
|
-
error: `Bark notification failed: ${error}`
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (this.config.shortcutName) {
|
|
232
|
-
try {
|
|
233
|
-
await this.sendShortcutNotification(this.config.shortcutName, options);
|
|
234
|
-
return {
|
|
235
|
-
success: true,
|
|
236
|
-
method: "shortcut"
|
|
237
|
-
};
|
|
238
|
-
} catch (error) {
|
|
239
|
-
return {
|
|
240
|
-
success: false,
|
|
241
|
-
method: "none",
|
|
242
|
-
error: `Shortcut notification failed: ${error}`
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return {
|
|
247
|
-
success: false,
|
|
248
|
-
method: "none",
|
|
249
|
-
error: "No notification method configured"
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
// ==========================================================================
|
|
253
|
-
// Configuration Management
|
|
254
|
-
// ==========================================================================
|
|
255
|
-
/**
|
|
256
|
-
* Update service configuration
|
|
257
|
-
*
|
|
258
|
-
* @param config - Partial configuration to update
|
|
259
|
-
*/
|
|
260
|
-
updateConfig(config) {
|
|
261
|
-
this.config = { ...this.config, ...config };
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Get current configuration
|
|
265
|
-
*
|
|
266
|
-
* @returns Current configuration
|
|
267
|
-
*/
|
|
268
|
-
getConfig() {
|
|
269
|
-
return { ...this.config };
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
async function loadLocalNotificationConfig() {
|
|
273
|
-
try {
|
|
274
|
-
if (!fs.existsSync(CONFIG_FILE)) {
|
|
275
|
-
return { ...DEFAULT_CONFIG };
|
|
276
|
-
}
|
|
277
|
-
const content = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
278
|
-
const config = JSON.parse(content);
|
|
279
|
-
return {
|
|
280
|
-
...DEFAULT_CONFIG,
|
|
281
|
-
...config
|
|
282
|
-
};
|
|
283
|
-
} catch (error) {
|
|
284
|
-
console.error("Failed to load local notification config:", error);
|
|
285
|
-
return { ...DEFAULT_CONFIG };
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
async function saveLocalNotificationConfig(config) {
|
|
289
|
-
try {
|
|
290
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
291
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
292
|
-
}
|
|
293
|
-
const existingConfig = await loadLocalNotificationConfig();
|
|
294
|
-
const newConfig = { ...existingConfig, ...config };
|
|
295
|
-
writeFileAtomic(CONFIG_FILE, JSON.stringify(newConfig, null, 2));
|
|
296
|
-
} catch (error) {
|
|
297
|
-
throw new Error(`Failed to save local notification config: ${error}`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
let serviceInstance = null;
|
|
301
|
-
async function getLocalNotificationService() {
|
|
302
|
-
if (!serviceInstance) {
|
|
303
|
-
const config = await loadLocalNotificationConfig();
|
|
304
|
-
serviceInstance = new LocalNotificationService(config);
|
|
305
|
-
}
|
|
306
|
-
return serviceInstance;
|
|
307
|
-
}
|
|
308
|
-
async function isShortcutsAvailable() {
|
|
309
|
-
if (process__default.platform !== "darwin") {
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
try {
|
|
313
|
-
await execAsync("which shortcuts", { timeout: 5e3 });
|
|
314
|
-
return true;
|
|
315
|
-
} catch {
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
async function listShortcuts() {
|
|
320
|
-
if (process__default.platform !== "darwin") {
|
|
321
|
-
return [];
|
|
322
|
-
}
|
|
323
|
-
try {
|
|
324
|
-
const { stdout } = await execAsync("shortcuts list", { timeout: 1e4 });
|
|
325
|
-
return stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
326
|
-
} catch {
|
|
327
|
-
return [];
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
function isValidBarkUrl(url) {
|
|
331
|
-
if (!url) {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
try {
|
|
335
|
-
const parsed = new URL(url);
|
|
336
|
-
return parsed.protocol === "https:" || parsed.protocol === "http:";
|
|
337
|
-
} catch {
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const DEFAULT_NOTIFICATION_CONFIG = {
|
|
343
|
-
enabled: false,
|
|
344
|
-
deviceToken: "",
|
|
345
|
-
threshold: 10,
|
|
346
|
-
// 10 minutes
|
|
347
|
-
cloudEndpoint: `${CLOUD_ENDPOINTS.REMOTE.BASE_URL}/api/v1`,
|
|
348
|
-
channels: {},
|
|
349
|
-
quietHours: {
|
|
350
|
-
enabled: false,
|
|
351
|
-
startHour: 22,
|
|
352
|
-
endHour: 8
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
function validateNotificationConfig(config) {
|
|
356
|
-
const errors = [];
|
|
357
|
-
const warnings = [];
|
|
358
|
-
if (config.threshold !== void 0) {
|
|
359
|
-
if (config.threshold < 1) {
|
|
360
|
-
errors.push({
|
|
361
|
-
field: "threshold",
|
|
362
|
-
message: "Threshold must be at least 1 minute",
|
|
363
|
-
code: "THRESHOLD_TOO_LOW"
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
if (config.threshold > 1440) {
|
|
367
|
-
warnings.push("Threshold is set to more than 24 hours");
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
if (config.channels?.feishu?.enabled) {
|
|
371
|
-
if (!config.channels.feishu.webhookUrl) {
|
|
372
|
-
errors.push({
|
|
373
|
-
field: "channels.feishu.webhookUrl",
|
|
374
|
-
message: "Feishu webhook URL is required when enabled",
|
|
375
|
-
code: "FEISHU_WEBHOOK_REQUIRED"
|
|
376
|
-
});
|
|
377
|
-
} else if (!config.channels.feishu.webhookUrl.startsWith("https://")) {
|
|
378
|
-
errors.push({
|
|
379
|
-
field: "channels.feishu.webhookUrl",
|
|
380
|
-
message: "Feishu webhook URL must use HTTPS",
|
|
381
|
-
code: "FEISHU_WEBHOOK_INVALID"
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
if (config.channels?.wechat?.enabled) {
|
|
386
|
-
if (!config.channels.wechat.corpId) {
|
|
387
|
-
errors.push({
|
|
388
|
-
field: "channels.wechat.corpId",
|
|
389
|
-
message: "WeChat Work Corp ID is required when enabled",
|
|
390
|
-
code: "WECHAT_CORPID_REQUIRED"
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
if (!config.channels.wechat.agentId) {
|
|
394
|
-
errors.push({
|
|
395
|
-
field: "channels.wechat.agentId",
|
|
396
|
-
message: "WeChat Work Agent ID is required when enabled",
|
|
397
|
-
code: "WECHAT_AGENTID_REQUIRED"
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
if (!config.channels.wechat.secret) {
|
|
401
|
-
errors.push({
|
|
402
|
-
field: "channels.wechat.secret",
|
|
403
|
-
message: "WeChat Work Secret is required when enabled",
|
|
404
|
-
code: "WECHAT_SECRET_REQUIRED"
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (config.channels?.email?.enabled) {
|
|
409
|
-
if (!config.channels.email.address) {
|
|
410
|
-
errors.push({
|
|
411
|
-
field: "channels.email.address",
|
|
412
|
-
message: "Email address is required when enabled",
|
|
413
|
-
code: "EMAIL_ADDRESS_REQUIRED"
|
|
414
|
-
});
|
|
415
|
-
} else if (!/^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(config.channels.email.address)) {
|
|
416
|
-
errors.push({
|
|
417
|
-
field: "channels.email.address",
|
|
418
|
-
message: "Invalid email address format",
|
|
419
|
-
code: "EMAIL_ADDRESS_INVALID"
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
if (config.channels?.sms?.enabled) {
|
|
424
|
-
if (!config.channels.sms.phone) {
|
|
425
|
-
errors.push({
|
|
426
|
-
field: "channels.sms.phone",
|
|
427
|
-
message: "Phone number is required when enabled",
|
|
428
|
-
code: "SMS_PHONE_REQUIRED"
|
|
429
|
-
});
|
|
430
|
-
} else if (!/^\d{10,15}$/.test(config.channels.sms.phone.replace(/\D/g, ""))) {
|
|
431
|
-
errors.push({
|
|
432
|
-
field: "channels.sms.phone",
|
|
433
|
-
message: "Invalid phone number format",
|
|
434
|
-
code: "SMS_PHONE_INVALID"
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
if (config.quietHours?.enabled) {
|
|
439
|
-
if (config.quietHours.startHour < 0 || config.quietHours.startHour > 23) {
|
|
440
|
-
errors.push({
|
|
441
|
-
field: "quietHours.startHour",
|
|
442
|
-
message: "Start hour must be between 0 and 23",
|
|
443
|
-
code: "QUIET_HOURS_INVALID"
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
if (config.quietHours.endHour < 0 || config.quietHours.endHour > 23) {
|
|
447
|
-
errors.push({
|
|
448
|
-
field: "quietHours.endHour",
|
|
449
|
-
message: "End hour must be between 0 and 23",
|
|
450
|
-
code: "QUIET_HOURS_INVALID"
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return {
|
|
455
|
-
valid: errors.length === 0,
|
|
456
|
-
errors,
|
|
457
|
-
warnings
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const CCJK_CONFIG_DIR = path__default.join(os__default.homedir(), ".ccjk");
|
|
462
|
-
const CONFIG_FILE_PATH = path__default.join(CCJK_CONFIG_DIR, "config.toml");
|
|
463
|
-
const SECRETS_FILE_PATH = path__default.join(CCJK_CONFIG_DIR, ".notification-secrets");
|
|
464
|
-
async function loadNotificationConfig() {
|
|
465
|
-
try {
|
|
466
|
-
if (!fs__default.existsSync(CCJK_CONFIG_DIR)) {
|
|
467
|
-
fs__default.mkdirSync(CCJK_CONFIG_DIR, { recursive: true });
|
|
468
|
-
}
|
|
469
|
-
if (!fs__default.existsSync(CONFIG_FILE_PATH)) {
|
|
470
|
-
return { ...DEFAULT_NOTIFICATION_CONFIG };
|
|
471
|
-
}
|
|
472
|
-
const configContent = fs__default.readFileSync(CONFIG_FILE_PATH, "utf-8");
|
|
473
|
-
const config = parse(configContent);
|
|
474
|
-
const notificationConfig = config.notification;
|
|
475
|
-
if (!notificationConfig) {
|
|
476
|
-
return { ...DEFAULT_NOTIFICATION_CONFIG };
|
|
477
|
-
}
|
|
478
|
-
const deviceToken = await loadDeviceToken();
|
|
479
|
-
const quietHoursConfig = notificationConfig.quietHours || {};
|
|
480
|
-
const defaultQuietHours = DEFAULT_NOTIFICATION_CONFIG.quietHours || {
|
|
481
|
-
enabled: false,
|
|
482
|
-
startHour: 22,
|
|
483
|
-
endHour: 8,
|
|
484
|
-
timezone: "local"
|
|
485
|
-
};
|
|
486
|
-
return {
|
|
487
|
-
...DEFAULT_NOTIFICATION_CONFIG,
|
|
488
|
-
...notificationConfig,
|
|
489
|
-
deviceToken: deviceToken || notificationConfig.deviceToken || "",
|
|
490
|
-
channels: {
|
|
491
|
-
...DEFAULT_NOTIFICATION_CONFIG.channels,
|
|
492
|
-
...notificationConfig.channels
|
|
493
|
-
},
|
|
494
|
-
quietHours: {
|
|
495
|
-
enabled: quietHoursConfig.enabled ?? defaultQuietHours.enabled,
|
|
496
|
-
startHour: quietHoursConfig.startHour ?? defaultQuietHours.startHour,
|
|
497
|
-
endHour: quietHoursConfig.endHour ?? defaultQuietHours.endHour,
|
|
498
|
-
timezone: quietHoursConfig.timezone ?? defaultQuietHours.timezone
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
} catch (error) {
|
|
502
|
-
console.error("Failed to load notification config:", error);
|
|
503
|
-
return { ...DEFAULT_NOTIFICATION_CONFIG };
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
async function loadDeviceToken() {
|
|
507
|
-
try {
|
|
508
|
-
if (!fs__default.existsSync(SECRETS_FILE_PATH)) {
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
const secretsContent = fs__default.readFileSync(SECRETS_FILE_PATH, "utf-8");
|
|
512
|
-
const secrets = JSON.parse(secretsContent);
|
|
513
|
-
if (!secrets.deviceToken) {
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
const decryptedToken = decryptToken(secrets.deviceToken);
|
|
517
|
-
return decryptedToken;
|
|
518
|
-
} catch {
|
|
519
|
-
return null;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
async function saveNotificationConfig(config) {
|
|
523
|
-
try {
|
|
524
|
-
if (!fs__default.existsSync(CCJK_CONFIG_DIR)) {
|
|
525
|
-
fs__default.mkdirSync(CCJK_CONFIG_DIR, { recursive: true });
|
|
526
|
-
}
|
|
527
|
-
let existingConfig = {};
|
|
528
|
-
if (fs__default.existsSync(CONFIG_FILE_PATH)) {
|
|
529
|
-
const configContent = fs__default.readFileSync(CONFIG_FILE_PATH, "utf-8");
|
|
530
|
-
existingConfig = parse(configContent);
|
|
531
|
-
}
|
|
532
|
-
const notificationConfig = { ...config };
|
|
533
|
-
if (notificationConfig.deviceToken) {
|
|
534
|
-
await saveDeviceToken(notificationConfig.deviceToken);
|
|
535
|
-
delete notificationConfig.deviceToken;
|
|
536
|
-
}
|
|
537
|
-
existingConfig.notification = {
|
|
538
|
-
...existingConfig.notification || {},
|
|
539
|
-
...notificationConfig
|
|
540
|
-
};
|
|
541
|
-
const tomlContent = stringify(existingConfig);
|
|
542
|
-
writeFileAtomic(CONFIG_FILE_PATH, tomlContent);
|
|
543
|
-
} catch (error) {
|
|
544
|
-
throw new Error(`Failed to save notification config: ${error}`);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
async function saveDeviceToken(token) {
|
|
548
|
-
try {
|
|
549
|
-
const encryptedToken = encryptToken(token);
|
|
550
|
-
let secrets = {};
|
|
551
|
-
if (fs__default.existsSync(SECRETS_FILE_PATH)) {
|
|
552
|
-
const secretsContent = fs__default.readFileSync(SECRETS_FILE_PATH, "utf-8");
|
|
553
|
-
secrets = JSON.parse(secretsContent);
|
|
554
|
-
}
|
|
555
|
-
secrets.deviceToken = encryptedToken;
|
|
556
|
-
secrets.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
557
|
-
writeFileAtomic(SECRETS_FILE_PATH, JSON.stringify(secrets, null, 2), {
|
|
558
|
-
encoding: "utf-8",
|
|
559
|
-
mode: 384
|
|
560
|
-
// Owner read/write only
|
|
561
|
-
});
|
|
562
|
-
} catch (error) {
|
|
563
|
-
throw new Error(`Failed to save device token: ${error}`);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
async function initializeNotificationConfig() {
|
|
567
|
-
const existingConfig = await loadNotificationConfig();
|
|
568
|
-
if (!existingConfig.deviceToken || !isValidTokenFormat(existingConfig.deviceToken)) {
|
|
569
|
-
existingConfig.deviceToken = generateDeviceToken();
|
|
570
|
-
await saveNotificationConfig(existingConfig);
|
|
571
|
-
}
|
|
572
|
-
return existingConfig;
|
|
573
|
-
}
|
|
574
|
-
async function updateNotificationConfig(updates) {
|
|
575
|
-
const currentConfig = await loadNotificationConfig();
|
|
576
|
-
const updatesQuietHours = updates.quietHours || {};
|
|
577
|
-
const currentQuietHours = currentConfig.quietHours || {
|
|
578
|
-
enabled: false,
|
|
579
|
-
startHour: 22,
|
|
580
|
-
endHour: 8,
|
|
581
|
-
timezone: "local"
|
|
582
|
-
};
|
|
583
|
-
const newConfig = {
|
|
584
|
-
...currentConfig,
|
|
585
|
-
...updates,
|
|
586
|
-
channels: {
|
|
587
|
-
...currentConfig.channels,
|
|
588
|
-
...updates.channels
|
|
589
|
-
},
|
|
590
|
-
quietHours: {
|
|
591
|
-
enabled: updatesQuietHours.enabled ?? currentQuietHours.enabled,
|
|
592
|
-
startHour: updatesQuietHours.startHour ?? currentQuietHours.startHour,
|
|
593
|
-
endHour: updatesQuietHours.endHour ?? currentQuietHours.endHour,
|
|
594
|
-
timezone: updatesQuietHours.timezone ?? currentQuietHours.timezone
|
|
595
|
-
}
|
|
596
|
-
};
|
|
597
|
-
await saveNotificationConfig(newConfig);
|
|
598
|
-
return newConfig;
|
|
599
|
-
}
|
|
600
|
-
async function enableChannel(channel, config) {
|
|
601
|
-
const currentConfig = await loadNotificationConfig();
|
|
602
|
-
currentConfig.channels[channel] = {
|
|
603
|
-
...config,
|
|
604
|
-
enabled: true
|
|
605
|
-
};
|
|
606
|
-
await saveNotificationConfig(currentConfig);
|
|
607
|
-
}
|
|
608
|
-
async function disableChannel(channel) {
|
|
609
|
-
const currentConfig = await loadNotificationConfig();
|
|
610
|
-
if (currentConfig.channels[channel]) {
|
|
611
|
-
currentConfig.channels[channel].enabled = false;
|
|
612
|
-
await saveNotificationConfig(currentConfig);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
async function getEnabledChannels() {
|
|
616
|
-
const config = await loadNotificationConfig();
|
|
617
|
-
const enabledChannels = [];
|
|
618
|
-
if (config.channels.feishu?.enabled) {
|
|
619
|
-
enabledChannels.push("feishu");
|
|
620
|
-
}
|
|
621
|
-
if (config.channels.wechat?.enabled) {
|
|
622
|
-
enabledChannels.push("wechat");
|
|
623
|
-
}
|
|
624
|
-
if (config.channels.email?.enabled) {
|
|
625
|
-
enabledChannels.push("email");
|
|
626
|
-
}
|
|
627
|
-
if (config.channels.sms?.enabled) {
|
|
628
|
-
enabledChannels.push("sms");
|
|
629
|
-
}
|
|
630
|
-
return enabledChannels;
|
|
631
|
-
}
|
|
632
|
-
async function validateCurrentConfig() {
|
|
633
|
-
const config = await loadNotificationConfig();
|
|
634
|
-
return validateNotificationConfig(config);
|
|
635
|
-
}
|
|
636
|
-
async function getConfigSummary() {
|
|
637
|
-
const config = await loadNotificationConfig();
|
|
638
|
-
const enabledChannels = await getEnabledChannels();
|
|
639
|
-
return {
|
|
640
|
-
enabled: config.enabled,
|
|
641
|
-
deviceToken: maskToken(config.deviceToken),
|
|
642
|
-
threshold: config.threshold,
|
|
643
|
-
enabledChannels,
|
|
644
|
-
quietHours: {
|
|
645
|
-
enabled: config.quietHours?.enabled || false,
|
|
646
|
-
hours: config.quietHours?.enabled ? `${config.quietHours.startHour}:00 - ${config.quietHours.endHour}:00` : void 0
|
|
647
|
-
}
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
const THRESHOLD_OPTIONS = [
|
|
651
|
-
{ value: 5, label: "5 minutes" },
|
|
652
|
-
{ value: 10, label: "10 minutes" },
|
|
653
|
-
{ value: 15, label: "15 minutes" },
|
|
654
|
-
{ value: 30, label: "30 minutes" },
|
|
655
|
-
{ value: 60, label: "1 hour" }
|
|
656
|
-
];
|
|
657
|
-
async function setThreshold(minutes) {
|
|
658
|
-
if (minutes < 1) {
|
|
659
|
-
throw new Error("Threshold must be at least 1 minute");
|
|
660
|
-
}
|
|
661
|
-
await updateNotificationConfig({ threshold: minutes });
|
|
662
|
-
}
|
|
663
|
-
async function enableNotifications() {
|
|
664
|
-
await updateNotificationConfig({ enabled: true });
|
|
665
|
-
}
|
|
666
|
-
async function disableNotifications() {
|
|
667
|
-
await updateNotificationConfig({ enabled: false });
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
const DEFAULT_CLOUD_ENDPOINT = `${CLOUD_ENDPOINTS.REMOTE.BASE_URL}/api/v1`;
|
|
671
|
-
const REQUEST_TIMEOUT = 3e4;
|
|
672
|
-
const POLL_TIMEOUT = 6e4;
|
|
673
|
-
class CloudClient {
|
|
674
|
-
static instance = null;
|
|
675
|
-
endpoint = DEFAULT_CLOUD_ENDPOINT;
|
|
676
|
-
deviceToken = "";
|
|
677
|
-
isPolling = false;
|
|
678
|
-
pollAbortController = null;
|
|
679
|
-
constructor() {
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Get the singleton instance
|
|
683
|
-
*/
|
|
684
|
-
static getInstance() {
|
|
685
|
-
if (!CloudClient.instance) {
|
|
686
|
-
CloudClient.instance = new CloudClient();
|
|
687
|
-
}
|
|
688
|
-
return CloudClient.instance;
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Initialize the cloud client
|
|
692
|
-
*/
|
|
693
|
-
async initialize() {
|
|
694
|
-
const config = await loadNotificationConfig();
|
|
695
|
-
this.endpoint = config.cloudEndpoint || DEFAULT_CLOUD_ENDPOINT;
|
|
696
|
-
this.deviceToken = config.deviceToken;
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Set the cloud endpoint
|
|
700
|
-
*/
|
|
701
|
-
setEndpoint(endpoint) {
|
|
702
|
-
this.endpoint = endpoint;
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* Set the device token
|
|
706
|
-
*/
|
|
707
|
-
setDeviceToken(token) {
|
|
708
|
-
this.deviceToken = token;
|
|
709
|
-
}
|
|
710
|
-
// ==========================================================================
|
|
711
|
-
// Device Registration
|
|
712
|
-
// ==========================================================================
|
|
713
|
-
/**
|
|
714
|
-
* Register this device with the cloud service
|
|
715
|
-
*/
|
|
716
|
-
async registerDevice(name) {
|
|
717
|
-
const deviceInfo = getDeviceInfo();
|
|
718
|
-
const config = await loadNotificationConfig();
|
|
719
|
-
const channelsArray = this.convertChannelsToArray(config.channels);
|
|
720
|
-
const request = {
|
|
721
|
-
name: name || deviceInfo.name,
|
|
722
|
-
platform: deviceInfo.platform,
|
|
723
|
-
version: "1.0.0",
|
|
724
|
-
// Static version for cloud client
|
|
725
|
-
config: {
|
|
726
|
-
channels: channelsArray,
|
|
727
|
-
threshold: config.threshold
|
|
728
|
-
}
|
|
729
|
-
};
|
|
730
|
-
const response = await this.request(
|
|
731
|
-
"/device/register",
|
|
732
|
-
{
|
|
733
|
-
method: "POST",
|
|
734
|
-
body: JSON.stringify(request)
|
|
735
|
-
}
|
|
736
|
-
);
|
|
737
|
-
if (response.success && response.data) {
|
|
738
|
-
await updateNotificationConfig({
|
|
739
|
-
deviceToken: response.data.token
|
|
740
|
-
});
|
|
741
|
-
this.deviceToken = response.data.token;
|
|
742
|
-
return response.data;
|
|
743
|
-
}
|
|
744
|
-
throw new Error(response.error || "Failed to register device");
|
|
745
|
-
}
|
|
746
|
-
/**
|
|
747
|
-
* Get device info from cloud service
|
|
748
|
-
*/
|
|
749
|
-
async getDeviceInfo() {
|
|
750
|
-
const response = await this.request(
|
|
751
|
-
"/device/info",
|
|
752
|
-
{ method: "GET" }
|
|
753
|
-
);
|
|
754
|
-
if (response.success && response.data) {
|
|
755
|
-
return response.data;
|
|
756
|
-
}
|
|
757
|
-
throw new Error(response.error || "Failed to get device info");
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* Update device channels on cloud service
|
|
761
|
-
*/
|
|
762
|
-
async updateChannels(channels) {
|
|
763
|
-
const channelsArray = this.convertChannelsToArray(channels);
|
|
764
|
-
const response = await this.request(
|
|
765
|
-
"/device/channels",
|
|
766
|
-
{
|
|
767
|
-
method: "PUT",
|
|
768
|
-
body: JSON.stringify({ channels: channelsArray })
|
|
769
|
-
}
|
|
770
|
-
);
|
|
771
|
-
if (!response.success) {
|
|
772
|
-
throw new Error(response.error || "Failed to update channels");
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
// ==========================================================================
|
|
776
|
-
// Notification Sending
|
|
777
|
-
// ==========================================================================
|
|
778
|
-
/**
|
|
779
|
-
* Send a notification through the cloud service
|
|
780
|
-
*/
|
|
781
|
-
async sendNotification(message, channels) {
|
|
782
|
-
const config = await loadNotificationConfig();
|
|
783
|
-
const targetChannels = channels || this.getEnabledChannelsFromConfig(config.channels);
|
|
784
|
-
if (targetChannels.length === 0) {
|
|
785
|
-
return [];
|
|
786
|
-
}
|
|
787
|
-
const title = message.title || this.generateTitle(message.type);
|
|
788
|
-
const body = this.generateBody(message);
|
|
789
|
-
const response = await this.request(
|
|
790
|
-
"/notify",
|
|
791
|
-
{
|
|
792
|
-
method: "POST",
|
|
793
|
-
body: JSON.stringify({
|
|
794
|
-
type: message.type,
|
|
795
|
-
title,
|
|
796
|
-
body,
|
|
797
|
-
task: message.task,
|
|
798
|
-
channels: targetChannels,
|
|
799
|
-
actions: message.actions,
|
|
800
|
-
priority: message.priority
|
|
801
|
-
})
|
|
802
|
-
}
|
|
803
|
-
);
|
|
804
|
-
if (response.success && response.data) {
|
|
805
|
-
return response.data.results;
|
|
806
|
-
}
|
|
807
|
-
return targetChannels.map((channel) => ({
|
|
808
|
-
success: false,
|
|
809
|
-
channel,
|
|
810
|
-
sentAt: /* @__PURE__ */ new Date(),
|
|
811
|
-
error: response.error || "Failed to send notification"
|
|
812
|
-
}));
|
|
813
|
-
}
|
|
814
|
-
/**
|
|
815
|
-
* Send a test notification
|
|
816
|
-
*/
|
|
817
|
-
async sendTestNotification() {
|
|
818
|
-
const response = await this.request(
|
|
819
|
-
"/notify/test",
|
|
820
|
-
{ method: "POST" }
|
|
821
|
-
);
|
|
822
|
-
if (response.success && response.data) {
|
|
823
|
-
return response.data.results;
|
|
824
|
-
}
|
|
825
|
-
throw new Error(response.error || "Failed to send test notification");
|
|
826
|
-
}
|
|
827
|
-
// ==========================================================================
|
|
828
|
-
// Reply Polling
|
|
829
|
-
// ==========================================================================
|
|
830
|
-
/**
|
|
831
|
-
* Start polling for replies
|
|
832
|
-
*
|
|
833
|
-
* @param onReply - Callback when a reply is received
|
|
834
|
-
* @param onError - Callback when an error occurs
|
|
835
|
-
*/
|
|
836
|
-
startPolling(onReply, onError) {
|
|
837
|
-
if (this.isPolling) {
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
this.isPolling = true;
|
|
841
|
-
this.pollLoop(onReply, onError);
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* Stop polling for replies
|
|
845
|
-
*/
|
|
846
|
-
stopPolling() {
|
|
847
|
-
this.isPolling = false;
|
|
848
|
-
if (this.pollAbortController) {
|
|
849
|
-
this.pollAbortController.abort();
|
|
850
|
-
this.pollAbortController = null;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* Poll loop for replies
|
|
855
|
-
*/
|
|
856
|
-
async pollLoop(onReply, onError) {
|
|
857
|
-
while (this.isPolling) {
|
|
858
|
-
try {
|
|
859
|
-
const reply = await this.pollForReply();
|
|
860
|
-
if (reply) {
|
|
861
|
-
onReply(reply);
|
|
862
|
-
}
|
|
863
|
-
} catch (error) {
|
|
864
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
865
|
-
break;
|
|
866
|
-
}
|
|
867
|
-
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
868
|
-
await this.sleep(5e3);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
/**
|
|
873
|
-
* Poll for a single reply (long-polling)
|
|
874
|
-
*/
|
|
875
|
-
async pollForReply() {
|
|
876
|
-
this.pollAbortController = new AbortController();
|
|
877
|
-
try {
|
|
878
|
-
const response = await this.request(
|
|
879
|
-
"/reply/poll",
|
|
880
|
-
{
|
|
881
|
-
method: "GET",
|
|
882
|
-
signal: this.pollAbortController.signal,
|
|
883
|
-
timeout: POLL_TIMEOUT
|
|
884
|
-
}
|
|
885
|
-
);
|
|
886
|
-
if (response.success && response.data?.reply) {
|
|
887
|
-
return {
|
|
888
|
-
...response.data.reply,
|
|
889
|
-
timestamp: new Date(response.data.reply.timestamp)
|
|
890
|
-
};
|
|
891
|
-
}
|
|
892
|
-
return null;
|
|
893
|
-
} finally {
|
|
894
|
-
this.pollAbortController = null;
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
/**
|
|
898
|
-
* Get reply for a specific notification
|
|
899
|
-
*/
|
|
900
|
-
async getReply(notificationId) {
|
|
901
|
-
const response = await this.request(
|
|
902
|
-
`/reply/${notificationId}`,
|
|
903
|
-
{ method: "GET" }
|
|
904
|
-
);
|
|
905
|
-
if (response.success && response.data?.reply) {
|
|
906
|
-
return {
|
|
907
|
-
...response.data.reply,
|
|
908
|
-
timestamp: new Date(response.data.reply.timestamp)
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
return null;
|
|
912
|
-
}
|
|
913
|
-
// ==========================================================================
|
|
914
|
-
// HTTP Request Helper
|
|
915
|
-
// ==========================================================================
|
|
916
|
-
/**
|
|
917
|
-
* Make an HTTP request to the cloud service
|
|
918
|
-
*/
|
|
919
|
-
async request(path, options) {
|
|
920
|
-
const url = `${this.endpoint}${path}`;
|
|
921
|
-
const timeout = options.timeout || REQUEST_TIMEOUT;
|
|
922
|
-
const timeoutController = new AbortController();
|
|
923
|
-
const timeoutId = setTimeout(() => timeoutController.abort(), timeout);
|
|
924
|
-
try {
|
|
925
|
-
const response = await fetch(url, {
|
|
926
|
-
method: options.method,
|
|
927
|
-
headers: {
|
|
928
|
-
"Content-Type": "application/json",
|
|
929
|
-
"X-Device-Token": this.deviceToken
|
|
930
|
-
},
|
|
931
|
-
body: options.body,
|
|
932
|
-
signal: options.signal || timeoutController.signal
|
|
933
|
-
});
|
|
934
|
-
clearTimeout(timeoutId);
|
|
935
|
-
const data = await response.json();
|
|
936
|
-
if (!response.ok) {
|
|
937
|
-
return {
|
|
938
|
-
success: false,
|
|
939
|
-
error: data.error || `HTTP ${response.status}: ${response.statusText}`,
|
|
940
|
-
code: data.code
|
|
941
|
-
};
|
|
942
|
-
}
|
|
943
|
-
return data;
|
|
944
|
-
} catch (error) {
|
|
945
|
-
clearTimeout(timeoutId);
|
|
946
|
-
if (error instanceof Error) {
|
|
947
|
-
if (error.name === "AbortError") {
|
|
948
|
-
throw error;
|
|
949
|
-
}
|
|
950
|
-
return {
|
|
951
|
-
success: false,
|
|
952
|
-
error: error.message,
|
|
953
|
-
code: "NETWORK_ERROR"
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
return {
|
|
957
|
-
success: false,
|
|
958
|
-
error: String(error),
|
|
959
|
-
code: "UNKNOWN_ERROR"
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
// ==========================================================================
|
|
964
|
-
// Utility Methods
|
|
965
|
-
// ==========================================================================
|
|
966
|
-
/**
|
|
967
|
-
* Get enabled channels from config
|
|
968
|
-
*/
|
|
969
|
-
getEnabledChannelsFromConfig(channels) {
|
|
970
|
-
const enabledChannels = [];
|
|
971
|
-
for (const [name, channelConfig] of Object.entries(channels)) {
|
|
972
|
-
if (channelConfig?.enabled) {
|
|
973
|
-
enabledChannels.push(name);
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
return enabledChannels;
|
|
977
|
-
}
|
|
978
|
-
/**
|
|
979
|
-
* Sleep for a specified duration
|
|
980
|
-
*/
|
|
981
|
-
sleep(ms) {
|
|
982
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
983
|
-
}
|
|
984
|
-
/**
|
|
985
|
-
* Convert channels from object format to array format
|
|
986
|
-
*
|
|
987
|
-
* Converts from: { feishu: { enabled: true, webhookUrl: "..." } }
|
|
988
|
-
* To: [{ type: "feishu", enabled: true, config: { webhookUrl: "..." } }]
|
|
989
|
-
*/
|
|
990
|
-
convertChannelsToArray(channels) {
|
|
991
|
-
const result = [];
|
|
992
|
-
for (const [channelType, channelData] of Object.entries(channels)) {
|
|
993
|
-
if (channelData && typeof channelData === "object") {
|
|
994
|
-
const { enabled, ...config } = channelData;
|
|
995
|
-
result.push({
|
|
996
|
-
type: channelType,
|
|
997
|
-
enabled: Boolean(enabled),
|
|
998
|
-
config
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
return result;
|
|
1003
|
-
}
|
|
1004
|
-
/**
|
|
1005
|
-
* Generate notification title based on type
|
|
1006
|
-
*/
|
|
1007
|
-
generateTitle(type) {
|
|
1008
|
-
const titles = {
|
|
1009
|
-
task_started: "Task Started",
|
|
1010
|
-
task_progress: "Task Progress",
|
|
1011
|
-
task_completed: "Task Completed",
|
|
1012
|
-
task_failed: "Task Failed",
|
|
1013
|
-
task_cancelled: "Task Cancelled",
|
|
1014
|
-
system: "System Notification"
|
|
1015
|
-
};
|
|
1016
|
-
return titles[type] || "Notification";
|
|
1017
|
-
}
|
|
1018
|
-
/**
|
|
1019
|
-
* Generate notification body from message
|
|
1020
|
-
*/
|
|
1021
|
-
generateBody(message) {
|
|
1022
|
-
const { task } = message;
|
|
1023
|
-
const lines = [];
|
|
1024
|
-
lines.push(`**Task**: ${task.description}`);
|
|
1025
|
-
lines.push(`**Status**: ${task.status}`);
|
|
1026
|
-
if (task.duration) {
|
|
1027
|
-
const minutes = Math.floor(task.duration / 6e4);
|
|
1028
|
-
const seconds = Math.floor(task.duration % 6e4 / 1e3);
|
|
1029
|
-
lines.push(`**Duration**: ${minutes}m ${seconds}s`);
|
|
1030
|
-
}
|
|
1031
|
-
if (task.result) {
|
|
1032
|
-
lines.push(`**Result**: ${task.result}`);
|
|
1033
|
-
}
|
|
1034
|
-
if (task.error) {
|
|
1035
|
-
lines.push(`**Error**: ${task.error}`);
|
|
1036
|
-
}
|
|
1037
|
-
return lines.join("\n");
|
|
1038
|
-
}
|
|
1039
|
-
/**
|
|
1040
|
-
* Reset the singleton instance (for testing)
|
|
1041
|
-
*/
|
|
1042
|
-
static resetInstance() {
|
|
1043
|
-
if (CloudClient.instance) {
|
|
1044
|
-
CloudClient.instance.stopPolling();
|
|
1045
|
-
CloudClient.instance = null;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
async function notificationCommand(action = "menu", args) {
|
|
1051
|
-
switch (action) {
|
|
1052
|
-
case "config":
|
|
1053
|
-
case "configure":
|
|
1054
|
-
await runConfigWizard();
|
|
1055
|
-
break;
|
|
1056
|
-
case "status":
|
|
1057
|
-
await showStatus();
|
|
1058
|
-
break;
|
|
1059
|
-
case "test":
|
|
1060
|
-
await sendTestNotification();
|
|
1061
|
-
break;
|
|
1062
|
-
case "enable":
|
|
1063
|
-
await enableNotifications();
|
|
1064
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:status.enabled")}`));
|
|
1065
|
-
break;
|
|
1066
|
-
case "disable":
|
|
1067
|
-
await disableNotifications();
|
|
1068
|
-
console.log(a.yellow(`\u23F8\uFE0F ${i18n.t("notification:status.disabled")}`));
|
|
1069
|
-
break;
|
|
1070
|
-
case "channels":
|
|
1071
|
-
await manageChannels();
|
|
1072
|
-
break;
|
|
1073
|
-
case "threshold":
|
|
1074
|
-
await configureThreshold();
|
|
1075
|
-
break;
|
|
1076
|
-
case "bind":
|
|
1077
|
-
console.log(a.yellow(i18n.t("notification:cloud.migrateToRemoteSetup")));
|
|
1078
|
-
await handleBind(args?.[0]);
|
|
1079
|
-
break;
|
|
1080
|
-
case "unbind":
|
|
1081
|
-
await handleUnbind();
|
|
1082
|
-
break;
|
|
1083
|
-
case "cloud-status":
|
|
1084
|
-
await showCloudStatus();
|
|
1085
|
-
break;
|
|
1086
|
-
case "local-config":
|
|
1087
|
-
await configureLocalNotification();
|
|
1088
|
-
break;
|
|
1089
|
-
case "local-test":
|
|
1090
|
-
await testLocalNotification();
|
|
1091
|
-
break;
|
|
1092
|
-
case "menu":
|
|
1093
|
-
default:
|
|
1094
|
-
await showNotificationMenu();
|
|
1095
|
-
break;
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
async function showNotificationMenu() {
|
|
1099
|
-
const config = await loadNotificationConfig();
|
|
1100
|
-
const enabledChannels = await getEnabledChannels();
|
|
1101
|
-
const cloudBound = isDeviceBound();
|
|
1102
|
-
console.log("");
|
|
1103
|
-
console.log(a.bold.cyan(i18n.t("notification:menu.title")));
|
|
1104
|
-
console.log("");
|
|
1105
|
-
const statusText = config.enabled ? a.green(i18n.t("notification:status.enabled")) : a.yellow(i18n.t("notification:status.disabled"));
|
|
1106
|
-
console.log(` ${a.dim(i18n.t("notification:menu.statusLabel"))} ${statusText}`);
|
|
1107
|
-
const cloudStatusText = cloudBound ? a.green(i18n.t("notification:cloud.bound")) : a.yellow(i18n.t("notification:cloud.notBound"));
|
|
1108
|
-
console.log(` ${a.dim(i18n.t("notification:cloud.statusLabel"))} ${cloudStatusText}`);
|
|
1109
|
-
if (enabledChannels.length > 0) {
|
|
1110
|
-
const channelNames = enabledChannels.map((ch) => i18n.t(`notification:channels.${ch}`)).join(", ");
|
|
1111
|
-
console.log(` ${a.dim(i18n.t("notification:menu.channelsLabel"))} ${channelNames}`);
|
|
1112
|
-
} else {
|
|
1113
|
-
console.log(` ${a.dim(i18n.t("notification:menu.channelsLabel"))} ${a.yellow(i18n.t("notification:channels.noChannels"))}`);
|
|
1114
|
-
}
|
|
1115
|
-
console.log(` ${a.dim(i18n.t("notification:menu.thresholdLabel"))} ${config.threshold} ${i18n.t("notification:config.threshold.minutes", { count: config.threshold })}`);
|
|
1116
|
-
console.log("");
|
|
1117
|
-
const choices = [];
|
|
1118
|
-
if (!cloudBound) {
|
|
1119
|
-
choices.push({ name: `\u{1F517} ${i18n.t("notification:cloud.bindDevice")}`, value: "bind" });
|
|
1120
|
-
} else {
|
|
1121
|
-
choices.push({ name: `\u2601\uFE0F ${i18n.t("notification:cloud.viewStatus")}`, value: "cloud-status" });
|
|
1122
|
-
}
|
|
1123
|
-
choices.push({
|
|
1124
|
-
name: config.enabled ? `\u23F8\uFE0F ${i18n.t("notification:menu.disable")}` : `\u25B6\uFE0F ${i18n.t("notification:menu.enable")}`,
|
|
1125
|
-
value: config.enabled ? "disable" : "enable"
|
|
1126
|
-
});
|
|
1127
|
-
choices.push(
|
|
1128
|
-
{ name: `\u2699\uFE0F ${i18n.t("notification:menu.configWizard")}`, value: "config" },
|
|
1129
|
-
{ name: `\u{1F4F1} ${i18n.t("notification:menu.manageChannels")}`, value: "channels" },
|
|
1130
|
-
{ name: `\u{1F514} ${i18n.t("notification:local.menuTitle")}`, value: "local-config" },
|
|
1131
|
-
{ name: `\u23F1\uFE0F ${i18n.t("notification:menu.setThreshold")}`, value: "threshold" },
|
|
1132
|
-
{ name: `\u{1F4CA} ${i18n.t("notification:menu.viewStatus")}`, value: "status" },
|
|
1133
|
-
{ name: `\u{1F9EA} ${i18n.t("notification:menu.sendTest")}`, value: "test" }
|
|
1134
|
-
);
|
|
1135
|
-
if (cloudBound) {
|
|
1136
|
-
choices.push({ name: `\u{1F513} ${i18n.t("notification:cloud.unbindDevice")}`, value: "unbind" });
|
|
1137
|
-
}
|
|
1138
|
-
choices.push({ name: `\u2190 ${i18n.t("notification:menu.back")}`, value: "back" });
|
|
1139
|
-
const { action } = await inquirer.prompt([{
|
|
1140
|
-
type: "list",
|
|
1141
|
-
name: "action",
|
|
1142
|
-
message: i18n.t("notification:menu.selectAction"),
|
|
1143
|
-
choices
|
|
1144
|
-
}]);
|
|
1145
|
-
if (action === "back") {
|
|
1146
|
-
return;
|
|
1147
|
-
}
|
|
1148
|
-
await notificationCommand(action);
|
|
1149
|
-
}
|
|
1150
|
-
async function runConfigWizard() {
|
|
1151
|
-
console.log("");
|
|
1152
|
-
console.log(a.bold.cyan(i18n.t("notification:config.wizard.title")));
|
|
1153
|
-
console.log(a.dim(i18n.t("notification:config.wizard.welcome")));
|
|
1154
|
-
console.log("");
|
|
1155
|
-
console.log(a.yellow(i18n.t("notification:config.wizard.step1")));
|
|
1156
|
-
const config = await initializeNotificationConfig();
|
|
1157
|
-
console.log(a.green(i18n.t("notification:config.wizard.tokenGenerated", { token: maskToken(config.deviceToken) })));
|
|
1158
|
-
console.log("");
|
|
1159
|
-
console.log(a.yellow(i18n.t("notification:config.wizard.step2")));
|
|
1160
|
-
const channels = await selectChannels();
|
|
1161
|
-
console.log("");
|
|
1162
|
-
if (channels.length > 0) {
|
|
1163
|
-
console.log(a.yellow(i18n.t("notification:config.wizard.step3")));
|
|
1164
|
-
for (const channel of channels) {
|
|
1165
|
-
await configureChannel(channel);
|
|
1166
|
-
}
|
|
1167
|
-
console.log("");
|
|
1168
|
-
}
|
|
1169
|
-
console.log(a.yellow(i18n.t("notification:config.wizard.step4")));
|
|
1170
|
-
await configureThreshold();
|
|
1171
|
-
console.log("");
|
|
1172
|
-
console.log(a.yellow(i18n.t("notification:config.wizard.step5")));
|
|
1173
|
-
await updateNotificationConfig({ enabled: true });
|
|
1174
|
-
const { shouldTest } = await inquirer.prompt([{
|
|
1175
|
-
type: "confirm",
|
|
1176
|
-
name: "shouldTest",
|
|
1177
|
-
message: "\u662F\u5426\u53D1\u9001\u6D4B\u8BD5\u901A\u77E5?",
|
|
1178
|
-
default: true
|
|
1179
|
-
}]);
|
|
1180
|
-
if (shouldTest) {
|
|
1181
|
-
await sendTestNotification();
|
|
1182
|
-
}
|
|
1183
|
-
console.log("");
|
|
1184
|
-
console.log(a.bold.green(i18n.t("notification:config.wizard.complete")));
|
|
1185
|
-
console.log("");
|
|
1186
|
-
}
|
|
1187
|
-
async function selectChannels() {
|
|
1188
|
-
const choices = [
|
|
1189
|
-
{ name: `\u{1F4F1} ${i18n.t("notification:channels.feishu")}`, value: "feishu" },
|
|
1190
|
-
{ name: `\u{1F4AC} ${i18n.t("notification:channels.wechat")}`, value: "wechat" },
|
|
1191
|
-
{ name: `\u{1F4E7} ${i18n.t("notification:channels.email")}`, value: "email" },
|
|
1192
|
-
{ name: `\u{1F4F2} ${i18n.t("notification:channels.sms")}`, value: "sms" }
|
|
1193
|
-
];
|
|
1194
|
-
const selected = [];
|
|
1195
|
-
for (const choice of choices) {
|
|
1196
|
-
const { enable } = await inquirer.prompt([{
|
|
1197
|
-
type: "confirm",
|
|
1198
|
-
name: "enable",
|
|
1199
|
-
message: `\u542F\u7528 ${choice.name}?`,
|
|
1200
|
-
default: false
|
|
1201
|
-
}]);
|
|
1202
|
-
if (enable) {
|
|
1203
|
-
selected.push(choice.value);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
return selected;
|
|
1207
|
-
}
|
|
1208
|
-
async function configureChannel(channel) {
|
|
1209
|
-
console.log("");
|
|
1210
|
-
console.log(a.green(`\u914D\u7F6E ${i18n.t(`notification:channels.${channel}`)}:`));
|
|
1211
|
-
switch (channel) {
|
|
1212
|
-
case "feishu":
|
|
1213
|
-
await configureFeishu();
|
|
1214
|
-
break;
|
|
1215
|
-
case "wechat":
|
|
1216
|
-
await configureWechat();
|
|
1217
|
-
break;
|
|
1218
|
-
case "email":
|
|
1219
|
-
await configureEmail();
|
|
1220
|
-
break;
|
|
1221
|
-
case "sms":
|
|
1222
|
-
await configureSms();
|
|
1223
|
-
break;
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
async function configureFeishu() {
|
|
1227
|
-
const { webhookUrl } = await inquirer.prompt([{
|
|
1228
|
-
type: "input",
|
|
1229
|
-
name: "webhookUrl",
|
|
1230
|
-
message: i18n.t("notification:feishu.webhookUrl"),
|
|
1231
|
-
validate: (value) => {
|
|
1232
|
-
if (!value.startsWith("https://")) {
|
|
1233
|
-
return i18n.t("notification:errors.invalidWebhook");
|
|
1234
|
-
}
|
|
1235
|
-
return true;
|
|
1236
|
-
}
|
|
1237
|
-
}]);
|
|
1238
|
-
const { secret } = await inquirer.prompt([{
|
|
1239
|
-
type: "input",
|
|
1240
|
-
name: "secret",
|
|
1241
|
-
message: `${i18n.t("notification:feishu.secret")} (\u53EF\u9009\uFF0C\u76F4\u63A5\u56DE\u8F66\u8DF3\u8FC7):`
|
|
1242
|
-
}]);
|
|
1243
|
-
const config = {
|
|
1244
|
-
enabled: true,
|
|
1245
|
-
webhookUrl,
|
|
1246
|
-
...secret && { secret }
|
|
1247
|
-
};
|
|
1248
|
-
await enableChannel("feishu", config);
|
|
1249
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:channels.feishu")} ${i18n.t("notification:status.configured")}`));
|
|
1250
|
-
}
|
|
1251
|
-
async function configureWechat() {
|
|
1252
|
-
const { corpId } = await inquirer.prompt([{
|
|
1253
|
-
type: "input",
|
|
1254
|
-
name: "corpId",
|
|
1255
|
-
message: i18n.t("notification:wechat.corpId"),
|
|
1256
|
-
validate: (value) => !!value || i18n.t("notification:errors.invalidWebhook")
|
|
1257
|
-
}]);
|
|
1258
|
-
const { agentId } = await inquirer.prompt([{
|
|
1259
|
-
type: "input",
|
|
1260
|
-
name: "agentId",
|
|
1261
|
-
message: i18n.t("notification:wechat.agentId"),
|
|
1262
|
-
validate: (value) => !!value || i18n.t("notification:errors.invalidWebhook")
|
|
1263
|
-
}]);
|
|
1264
|
-
const { secret } = await inquirer.prompt([{
|
|
1265
|
-
type: "input",
|
|
1266
|
-
name: "secret",
|
|
1267
|
-
message: i18n.t("notification:wechat.secret"),
|
|
1268
|
-
validate: (value) => !!value || i18n.t("notification:errors.invalidWebhook")
|
|
1269
|
-
}]);
|
|
1270
|
-
const config = {
|
|
1271
|
-
enabled: true,
|
|
1272
|
-
corpId,
|
|
1273
|
-
agentId,
|
|
1274
|
-
secret
|
|
1275
|
-
};
|
|
1276
|
-
await enableChannel("wechat", config);
|
|
1277
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:channels.wechat")} ${i18n.t("notification:status.configured")}`));
|
|
1278
|
-
}
|
|
1279
|
-
async function configureEmail() {
|
|
1280
|
-
const { address } = await inquirer.prompt([{
|
|
1281
|
-
type: "input",
|
|
1282
|
-
name: "address",
|
|
1283
|
-
message: i18n.t("notification:email.address"),
|
|
1284
|
-
validate: (value) => {
|
|
1285
|
-
if (!/^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(value)) {
|
|
1286
|
-
return i18n.t("notification:errors.invalidEmail");
|
|
1287
|
-
}
|
|
1288
|
-
return true;
|
|
1289
|
-
}
|
|
1290
|
-
}]);
|
|
1291
|
-
const config = {
|
|
1292
|
-
enabled: true,
|
|
1293
|
-
address
|
|
1294
|
-
};
|
|
1295
|
-
await enableChannel("email", config);
|
|
1296
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:channels.email")} ${i18n.t("notification:status.configured")}`));
|
|
1297
|
-
}
|
|
1298
|
-
async function configureSms() {
|
|
1299
|
-
const { phone } = await inquirer.prompt([{
|
|
1300
|
-
type: "input",
|
|
1301
|
-
name: "phone",
|
|
1302
|
-
message: i18n.t("notification:sms.phone"),
|
|
1303
|
-
validate: (value) => {
|
|
1304
|
-
if (!/^\d{10,15}$/.test(value.replace(/\D/g, ""))) {
|
|
1305
|
-
return i18n.t("notification:errors.invalidPhone");
|
|
1306
|
-
}
|
|
1307
|
-
return true;
|
|
1308
|
-
}
|
|
1309
|
-
}]);
|
|
1310
|
-
const { countryCode } = await inquirer.prompt([{
|
|
1311
|
-
type: "input",
|
|
1312
|
-
name: "countryCode",
|
|
1313
|
-
message: i18n.t("notification:sms.countryCode"),
|
|
1314
|
-
default: "+86"
|
|
1315
|
-
}]);
|
|
1316
|
-
const config = {
|
|
1317
|
-
enabled: true,
|
|
1318
|
-
phone,
|
|
1319
|
-
countryCode
|
|
1320
|
-
};
|
|
1321
|
-
await enableChannel("sms", config);
|
|
1322
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:channels.sms")} ${i18n.t("notification:status.configured")}`));
|
|
1323
|
-
}
|
|
1324
|
-
async function manageChannels() {
|
|
1325
|
-
const enabledChannels = await getEnabledChannels();
|
|
1326
|
-
console.log("");
|
|
1327
|
-
console.log(a.bold.cyan(i18n.t("notification:channels.title")));
|
|
1328
|
-
console.log("");
|
|
1329
|
-
if (enabledChannels.length === 0) {
|
|
1330
|
-
console.log(a.yellow(i18n.t("notification:channels.noChannels")));
|
|
1331
|
-
} else {
|
|
1332
|
-
console.log(i18n.t("notification:channels.enabledCount", { count: enabledChannels.length }));
|
|
1333
|
-
for (const channel of enabledChannels) {
|
|
1334
|
-
console.log(` \u2705 ${i18n.t(`notification:channels.${channel}`)}`);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
console.log("");
|
|
1338
|
-
const { action } = await inquirer.prompt([{
|
|
1339
|
-
type: "list",
|
|
1340
|
-
name: "action",
|
|
1341
|
-
message: "\u9009\u62E9\u64CD\u4F5C:",
|
|
1342
|
-
choices: [
|
|
1343
|
-
{ name: "\u2795 \u6DFB\u52A0\u6E20\u9053", value: "add" },
|
|
1344
|
-
{ name: "\u2796 \u79FB\u9664\u6E20\u9053", value: "remove" },
|
|
1345
|
-
{ name: "\u2190 \u8FD4\u56DE", value: "back" }
|
|
1346
|
-
]
|
|
1347
|
-
}]);
|
|
1348
|
-
if (action === "back") {
|
|
1349
|
-
return;
|
|
1350
|
-
}
|
|
1351
|
-
if (action === "add") {
|
|
1352
|
-
const allChannels = ["feishu", "wechat", "email", "sms"];
|
|
1353
|
-
const availableChannels = allChannels.filter((ch) => !enabledChannels.includes(ch));
|
|
1354
|
-
if (availableChannels.length === 0) {
|
|
1355
|
-
console.log(a.yellow("\u6240\u6709\u6E20\u9053\u90FD\u5DF2\u542F\u7528"));
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
const { channel } = await inquirer.prompt([{
|
|
1359
|
-
type: "list",
|
|
1360
|
-
name: "channel",
|
|
1361
|
-
message: "\u9009\u62E9\u8981\u6DFB\u52A0\u7684\u6E20\u9053:",
|
|
1362
|
-
choices: availableChannels.map((ch) => ({
|
|
1363
|
-
name: i18n.t(`notification:channels.${ch}`),
|
|
1364
|
-
value: ch
|
|
1365
|
-
}))
|
|
1366
|
-
}]);
|
|
1367
|
-
await configureChannel(channel);
|
|
1368
|
-
} else if (action === "remove") {
|
|
1369
|
-
if (enabledChannels.length === 0) {
|
|
1370
|
-
console.log(a.yellow("\u6CA1\u6709\u5DF2\u542F\u7528\u7684\u6E20\u9053"));
|
|
1371
|
-
return;
|
|
1372
|
-
}
|
|
1373
|
-
const { channel } = await inquirer.prompt([{
|
|
1374
|
-
type: "list",
|
|
1375
|
-
name: "channel",
|
|
1376
|
-
message: "\u9009\u62E9\u8981\u79FB\u9664\u7684\u6E20\u9053:",
|
|
1377
|
-
choices: enabledChannels.map((ch) => ({
|
|
1378
|
-
name: i18n.t(`notification:channels.${ch}`),
|
|
1379
|
-
value: ch
|
|
1380
|
-
}))
|
|
1381
|
-
}]);
|
|
1382
|
-
await disableChannel(channel);
|
|
1383
|
-
console.log(a.green(`\u2705 \u5DF2\u79FB\u9664 ${i18n.t(`notification:channels.${channel}`)}`));
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
async function configureThreshold() {
|
|
1387
|
-
const config = await loadNotificationConfig();
|
|
1388
|
-
console.log("");
|
|
1389
|
-
console.log(a.dim(i18n.t("notification:config.threshold.description")));
|
|
1390
|
-
console.log("");
|
|
1391
|
-
const { threshold } = await inquirer.prompt([{
|
|
1392
|
-
type: "list",
|
|
1393
|
-
name: "threshold",
|
|
1394
|
-
message: i18n.t("notification:config.threshold.title"),
|
|
1395
|
-
choices: [
|
|
1396
|
-
...THRESHOLD_OPTIONS.map((opt) => ({
|
|
1397
|
-
name: opt.label,
|
|
1398
|
-
value: opt.value
|
|
1399
|
-
})),
|
|
1400
|
-
{ name: i18n.t("notification:config.threshold.custom"), value: -1 }
|
|
1401
|
-
],
|
|
1402
|
-
default: config.threshold
|
|
1403
|
-
}]);
|
|
1404
|
-
let finalThreshold = threshold;
|
|
1405
|
-
if (threshold === -1) {
|
|
1406
|
-
const { customValue } = await inquirer.prompt([{
|
|
1407
|
-
type: "input",
|
|
1408
|
-
name: "customValue",
|
|
1409
|
-
message: "\u8F93\u5165\u81EA\u5B9A\u4E49\u9608\u503C\uFF08\u5206\u949F\uFF09:",
|
|
1410
|
-
validate: (value) => {
|
|
1411
|
-
const num = Number.parseInt(value, 10);
|
|
1412
|
-
if (Number.isNaN(num) || num < 1) {
|
|
1413
|
-
return "\u8BF7\u8F93\u5165\u5927\u4E8E 0 \u7684\u6570\u5B57";
|
|
1414
|
-
}
|
|
1415
|
-
return true;
|
|
1416
|
-
}
|
|
1417
|
-
}]);
|
|
1418
|
-
finalThreshold = Number.parseInt(customValue, 10);
|
|
1419
|
-
}
|
|
1420
|
-
await setThreshold(finalThreshold);
|
|
1421
|
-
console.log(a.green(`\u2705 \u9608\u503C\u5DF2\u8BBE\u7F6E\u4E3A ${finalThreshold} \u5206\u949F`));
|
|
1422
|
-
}
|
|
1423
|
-
async function showStatus() {
|
|
1424
|
-
const summary = await getConfigSummary();
|
|
1425
|
-
const validation = await validateCurrentConfig();
|
|
1426
|
-
console.log("");
|
|
1427
|
-
console.log(a.bold.cyan("\u{1F4CA} \u901A\u77E5\u7CFB\u7EDF\u72B6\u6001"));
|
|
1428
|
-
console.log("");
|
|
1429
|
-
const statusIcon = summary.enabled ? "\u2705" : "\u23F8\uFE0F";
|
|
1430
|
-
const statusText = summary.enabled ? a.green(i18n.t("notification:status.enabled")) : a.yellow(i18n.t("notification:status.disabled"));
|
|
1431
|
-
console.log(` ${statusIcon} \u72B6\u6001: ${statusText}`);
|
|
1432
|
-
console.log(` \u{1F511} \u8BBE\u5907\u4EE4\u724C: ${a.dim(summary.deviceToken)}`);
|
|
1433
|
-
console.log(` \u23F1\uFE0F \u9608\u503C: ${summary.threshold} \u5206\u949F`);
|
|
1434
|
-
console.log("");
|
|
1435
|
-
console.log(a.bold(" \u{1F4F1} \u901A\u77E5\u6E20\u9053:"));
|
|
1436
|
-
if (summary.enabledChannels.length === 0) {
|
|
1437
|
-
console.log(` ${a.yellow(i18n.t("notification:channels.noChannels"))}`);
|
|
1438
|
-
} else {
|
|
1439
|
-
for (const channel of summary.enabledChannels) {
|
|
1440
|
-
console.log(` \u2705 ${i18n.t(`notification:channels.${channel}`)}`);
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
if (summary.quietHours.enabled) {
|
|
1444
|
-
console.log("");
|
|
1445
|
-
console.log(` \u{1F319} \u514D\u6253\u6270: ${summary.quietHours.hours}`);
|
|
1446
|
-
}
|
|
1447
|
-
if (!validation.valid) {
|
|
1448
|
-
console.log("");
|
|
1449
|
-
console.log(a.bold.red(" \u26A0\uFE0F \u914D\u7F6E\u95EE\u9898:"));
|
|
1450
|
-
for (const error of validation.errors) {
|
|
1451
|
-
console.log(` \u274C ${error.message}`);
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
if (validation.warnings.length > 0) {
|
|
1455
|
-
console.log("");
|
|
1456
|
-
console.log(a.bold.yellow(" \u26A0\uFE0F \u8B66\u544A:"));
|
|
1457
|
-
for (const warning of validation.warnings) {
|
|
1458
|
-
console.log(` \u26A0\uFE0F ${warning}`);
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
console.log("");
|
|
1462
|
-
}
|
|
1463
|
-
async function sendTestNotification() {
|
|
1464
|
-
const enabledChannels = await getEnabledChannels();
|
|
1465
|
-
if (enabledChannels.length === 0) {
|
|
1466
|
-
console.log(a.yellow(i18n.t("notification:errors.noChannels")));
|
|
1467
|
-
return;
|
|
1468
|
-
}
|
|
1469
|
-
console.log("");
|
|
1470
|
-
console.log(a.green(i18n.t("notification:test.sending")));
|
|
1471
|
-
try {
|
|
1472
|
-
const client = CloudClient.getInstance();
|
|
1473
|
-
await client.initialize();
|
|
1474
|
-
const results = await client.sendTestNotification();
|
|
1475
|
-
console.log("");
|
|
1476
|
-
let hasSuccess = false;
|
|
1477
|
-
let hasFailure = false;
|
|
1478
|
-
for (const result of results) {
|
|
1479
|
-
const channelName = i18n.t(`notification:channels.${result.channel}`);
|
|
1480
|
-
if (result.success) {
|
|
1481
|
-
console.log(a.green(`\u2705 ${channelName}: ${i18n.t("notification:test.success")}`));
|
|
1482
|
-
hasSuccess = true;
|
|
1483
|
-
} else {
|
|
1484
|
-
console.log(a.red(`\u274C ${channelName}: ${result.error || i18n.t("notification:test.failed")}`));
|
|
1485
|
-
hasFailure = true;
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
console.log("");
|
|
1489
|
-
if (hasSuccess) {
|
|
1490
|
-
console.log(a.dim(i18n.t("notification:test.checkDevice")));
|
|
1491
|
-
}
|
|
1492
|
-
if (hasFailure) {
|
|
1493
|
-
console.log(a.yellow(i18n.t("notification:test.partialFailure")));
|
|
1494
|
-
}
|
|
1495
|
-
} catch (error) {
|
|
1496
|
-
console.log("");
|
|
1497
|
-
console.log(a.red(`\u274C ${i18n.t("notification:errors.sendFailed")}`));
|
|
1498
|
-
if (error instanceof Error) {
|
|
1499
|
-
console.log(a.dim(error.message));
|
|
1500
|
-
}
|
|
1501
|
-
console.log("");
|
|
1502
|
-
console.log(a.yellow(i18n.t("notification:test.troubleshooting")));
|
|
1503
|
-
console.log(a.dim(` 1. ${i18n.t("notification:test.checkConnection")}`));
|
|
1504
|
-
console.log(a.dim(` 2. ${i18n.t("notification:test.checkConfig")}`));
|
|
1505
|
-
console.log(a.dim(` 3. ${i18n.t("notification:test.checkToken")}`));
|
|
1506
|
-
}
|
|
1507
|
-
console.log("");
|
|
1508
|
-
}
|
|
1509
|
-
async function handleBind(code) {
|
|
1510
|
-
console.log("");
|
|
1511
|
-
console.log(a.bold.cyan(i18n.t("notification:cloud.bindTitle")));
|
|
1512
|
-
console.log("");
|
|
1513
|
-
if (isDeviceBound()) {
|
|
1514
|
-
console.log(a.yellow(i18n.t("notification:cloud.alreadyBound")));
|
|
1515
|
-
console.log("");
|
|
1516
|
-
const { confirmRebind } = await inquirer.prompt([{
|
|
1517
|
-
type: "confirm",
|
|
1518
|
-
name: "confirmRebind",
|
|
1519
|
-
message: i18n.t("notification:cloud.confirmRebind"),
|
|
1520
|
-
default: false
|
|
1521
|
-
}]);
|
|
1522
|
-
if (!confirmRebind) {
|
|
1523
|
-
return;
|
|
1524
|
-
}
|
|
1525
|
-
unbindDevice();
|
|
1526
|
-
}
|
|
1527
|
-
let bindingCode = code;
|
|
1528
|
-
if (!bindingCode) {
|
|
1529
|
-
console.log(a.dim(i18n.t("notification:cloud.bindInstructions")));
|
|
1530
|
-
console.log("");
|
|
1531
|
-
const { inputCode } = await inquirer.prompt([{
|
|
1532
|
-
type: "input",
|
|
1533
|
-
name: "inputCode",
|
|
1534
|
-
message: i18n.t("notification:cloud.enterCode"),
|
|
1535
|
-
validate: (value) => {
|
|
1536
|
-
if (!value || value.trim().length === 0) {
|
|
1537
|
-
return i18n.t("notification:cloud.codeRequired");
|
|
1538
|
-
}
|
|
1539
|
-
if (value.trim().length < 4) {
|
|
1540
|
-
return i18n.t("notification:cloud.codeInvalid");
|
|
1541
|
-
}
|
|
1542
|
-
return true;
|
|
1543
|
-
}
|
|
1544
|
-
}]);
|
|
1545
|
-
bindingCode = inputCode.trim();
|
|
1546
|
-
}
|
|
1547
|
-
console.log("");
|
|
1548
|
-
console.log(a.green(i18n.t("notification:cloud.binding")));
|
|
1549
|
-
try {
|
|
1550
|
-
const result = await bindDevice(bindingCode);
|
|
1551
|
-
if (result.success) {
|
|
1552
|
-
console.log("");
|
|
1553
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:cloud.bindSuccess")}`));
|
|
1554
|
-
console.log("");
|
|
1555
|
-
console.log(a.dim(i18n.t("notification:cloud.deviceId", { id: result.data?.deviceId || "N/A" })));
|
|
1556
|
-
console.log("");
|
|
1557
|
-
const { sendTest } = await inquirer.prompt([{
|
|
1558
|
-
type: "confirm",
|
|
1559
|
-
name: "sendTest",
|
|
1560
|
-
message: i18n.t("notification:cloud.sendTestAfterBind"),
|
|
1561
|
-
default: true
|
|
1562
|
-
}]);
|
|
1563
|
-
if (sendTest) {
|
|
1564
|
-
await sendCloudTestNotification();
|
|
1565
|
-
}
|
|
1566
|
-
} else {
|
|
1567
|
-
console.log("");
|
|
1568
|
-
console.log(a.red(`\u274C ${i18n.t("notification:cloud.bindFailed")}`));
|
|
1569
|
-
console.log(a.dim(result.error || i18n.t("notification:cloud.unknownError")));
|
|
1570
|
-
console.log("");
|
|
1571
|
-
console.log(a.yellow(i18n.t("notification:cloud.bindTroubleshooting")));
|
|
1572
|
-
console.log(a.dim(` 1. ${i18n.t("notification:cloud.checkCode")}`));
|
|
1573
|
-
console.log(a.dim(` 2. ${i18n.t("notification:cloud.checkExpiry")}`));
|
|
1574
|
-
console.log(a.dim(` 3. ${i18n.t("notification:cloud.checkNetwork")}`));
|
|
1575
|
-
}
|
|
1576
|
-
} catch (error) {
|
|
1577
|
-
console.log("");
|
|
1578
|
-
console.log(a.red(`\u274C ${i18n.t("notification:cloud.bindError")}`));
|
|
1579
|
-
if (error instanceof Error) {
|
|
1580
|
-
console.log(a.dim(error.message));
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
console.log("");
|
|
1584
|
-
}
|
|
1585
|
-
async function handleUnbind() {
|
|
1586
|
-
console.log("");
|
|
1587
|
-
if (!isDeviceBound()) {
|
|
1588
|
-
console.log(a.yellow(i18n.t("notification:cloud.notBoundYet")));
|
|
1589
|
-
console.log("");
|
|
1590
|
-
return;
|
|
1591
|
-
}
|
|
1592
|
-
const { confirmUnbind } = await inquirer.prompt([{
|
|
1593
|
-
type: "confirm",
|
|
1594
|
-
name: "confirmUnbind",
|
|
1595
|
-
message: i18n.t("notification:cloud.confirmUnbind"),
|
|
1596
|
-
default: false
|
|
1597
|
-
}]);
|
|
1598
|
-
if (!confirmUnbind) {
|
|
1599
|
-
console.log(a.dim(i18n.t("notification:cloud.unbindCancelled")));
|
|
1600
|
-
console.log("");
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
|
-
unbindDevice();
|
|
1604
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:cloud.unbindSuccess")}`));
|
|
1605
|
-
console.log("");
|
|
1606
|
-
}
|
|
1607
|
-
async function showCloudStatus() {
|
|
1608
|
-
console.log("");
|
|
1609
|
-
console.log(a.bold.cyan(i18n.t("notification:cloud.statusTitle")));
|
|
1610
|
-
console.log("");
|
|
1611
|
-
const status = await getBindingStatus();
|
|
1612
|
-
if (!status.bound) {
|
|
1613
|
-
console.log(` ${a.yellow("\u26A0\uFE0F")} ${i18n.t("notification:cloud.notBound")}`);
|
|
1614
|
-
console.log("");
|
|
1615
|
-
console.log(a.dim(i18n.t("notification:cloud.bindHint")));
|
|
1616
|
-
console.log("");
|
|
1617
|
-
return;
|
|
1618
|
-
}
|
|
1619
|
-
console.log(` ${a.green("\u2705")} ${i18n.t("notification:cloud.bound")}`);
|
|
1620
|
-
console.log("");
|
|
1621
|
-
if (status.deviceId) {
|
|
1622
|
-
console.log(` ${a.dim(i18n.t("notification:cloud.deviceIdLabel"))} ${status.deviceId}`);
|
|
1623
|
-
}
|
|
1624
|
-
if (status.deviceInfo) {
|
|
1625
|
-
console.log(` ${a.dim(i18n.t("notification:cloud.deviceNameLabel"))} ${status.deviceInfo.name}`);
|
|
1626
|
-
console.log(` ${a.dim(i18n.t("notification:cloud.platformLabel"))} ${status.deviceInfo.platform}`);
|
|
1627
|
-
}
|
|
1628
|
-
if (status.lastUsed) {
|
|
1629
|
-
const lastUsedDate = new Date(status.lastUsed);
|
|
1630
|
-
console.log(` ${a.dim(i18n.t("notification:cloud.lastUsedLabel"))} ${lastUsedDate.toLocaleString()}`);
|
|
1631
|
-
}
|
|
1632
|
-
console.log("");
|
|
1633
|
-
}
|
|
1634
|
-
async function sendCloudTestNotification() {
|
|
1635
|
-
console.log("");
|
|
1636
|
-
console.log(a.green(i18n.t("notification:cloud.sendingTest")));
|
|
1637
|
-
try {
|
|
1638
|
-
const result = await sendNotification({
|
|
1639
|
-
title: i18n.t("notification:cloud.testTitle"),
|
|
1640
|
-
body: i18n.t("notification:cloud.testBody"),
|
|
1641
|
-
type: "success"
|
|
1642
|
-
});
|
|
1643
|
-
if (result.success) {
|
|
1644
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:cloud.testSuccess")}`));
|
|
1645
|
-
console.log(a.dim(i18n.t("notification:cloud.checkPhone")));
|
|
1646
|
-
} else {
|
|
1647
|
-
console.log(a.red(`\u274C ${i18n.t("notification:cloud.testFailed")}`));
|
|
1648
|
-
console.log(a.dim(result.error || i18n.t("notification:cloud.unknownError")));
|
|
1649
|
-
}
|
|
1650
|
-
} catch (error) {
|
|
1651
|
-
console.log(a.red(`\u274C ${i18n.t("notification:cloud.testError")}`));
|
|
1652
|
-
if (error instanceof Error) {
|
|
1653
|
-
console.log(a.dim(error.message));
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
console.log("");
|
|
1657
|
-
}
|
|
1658
|
-
async function configureLocalNotification() {
|
|
1659
|
-
const config = await loadLocalNotificationConfig();
|
|
1660
|
-
const shortcutsAvailable = await isShortcutsAvailable();
|
|
1661
|
-
console.log("");
|
|
1662
|
-
console.log(a.bold.cyan(i18n.t("notification:local.title")));
|
|
1663
|
-
console.log(a.dim(i18n.t("notification:local.description")));
|
|
1664
|
-
console.log("");
|
|
1665
|
-
console.log(a.bold(i18n.t("notification:local.currentStatus")));
|
|
1666
|
-
if (shortcutsAvailable) {
|
|
1667
|
-
const shortcutsStatus = config.shortcutName ? a.green(i18n.t("notification:status.enabled")) : a.yellow(i18n.t("notification:status.disabled"));
|
|
1668
|
-
console.log(` \u{1F34E} ${i18n.t("notification:local.shortcuts.name")}: ${shortcutsStatus}`);
|
|
1669
|
-
if (config.shortcutName) {
|
|
1670
|
-
console.log(` ${a.dim(i18n.t("notification:local.shortcuts.currentShortcut", { name: config.shortcutName }))}`);
|
|
1671
|
-
}
|
|
1672
|
-
} else {
|
|
1673
|
-
console.log(` \u{1F34E} ${i18n.t("notification:local.shortcuts.name")}: ${a.dim(i18n.t("notification:local.shortcuts.notAvailable"))}`);
|
|
1674
|
-
}
|
|
1675
|
-
const barkStatus = config.barkUrl ? a.green(i18n.t("notification:status.enabled")) : a.yellow(i18n.t("notification:status.disabled"));
|
|
1676
|
-
console.log(` \u{1F4F1} ${i18n.t("notification:local.bark.name")}: ${barkStatus}`);
|
|
1677
|
-
if (config.barkUrl) {
|
|
1678
|
-
console.log(` ${a.dim(i18n.t("notification:local.bark.currentServer", { url: config.barkUrl }))}`);
|
|
1679
|
-
}
|
|
1680
|
-
console.log("");
|
|
1681
|
-
const choices = [];
|
|
1682
|
-
if (shortcutsAvailable) {
|
|
1683
|
-
choices.push({
|
|
1684
|
-
name: config.shortcutName ? `\u{1F34E} ${i18n.t("notification:local.shortcuts.configure")}` : `\u{1F34E} ${i18n.t("notification:local.shortcuts.enable")}`,
|
|
1685
|
-
value: "shortcuts"
|
|
1686
|
-
});
|
|
1687
|
-
}
|
|
1688
|
-
choices.push({
|
|
1689
|
-
name: config.barkUrl ? `\u{1F4F1} ${i18n.t("notification:local.bark.configure")}` : `\u{1F4F1} ${i18n.t("notification:local.bark.enable")}`,
|
|
1690
|
-
value: "bark"
|
|
1691
|
-
});
|
|
1692
|
-
choices.push(
|
|
1693
|
-
{ name: `\u{1F9EA} ${i18n.t("notification:local.testLocal")}`, value: "test" },
|
|
1694
|
-
{ name: `\u2190 ${i18n.t("notification:menu.back")}`, value: "back" }
|
|
1695
|
-
);
|
|
1696
|
-
const { action } = await inquirer.prompt([{
|
|
1697
|
-
type: "list",
|
|
1698
|
-
name: "action",
|
|
1699
|
-
message: i18n.t("notification:menu.selectAction"),
|
|
1700
|
-
choices
|
|
1701
|
-
}]);
|
|
1702
|
-
if (action === "back") {
|
|
1703
|
-
return;
|
|
1704
|
-
}
|
|
1705
|
-
switch (action) {
|
|
1706
|
-
case "shortcuts":
|
|
1707
|
-
await configureShortcuts();
|
|
1708
|
-
break;
|
|
1709
|
-
case "bark":
|
|
1710
|
-
await configureBark();
|
|
1711
|
-
break;
|
|
1712
|
-
case "test":
|
|
1713
|
-
await testLocalNotification();
|
|
1714
|
-
break;
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
async function configureShortcuts() {
|
|
1718
|
-
console.log("");
|
|
1719
|
-
console.log(a.bold.cyan(i18n.t("notification:local.shortcuts.title")));
|
|
1720
|
-
console.log(a.dim(i18n.t("notification:local.shortcuts.description")));
|
|
1721
|
-
console.log("");
|
|
1722
|
-
console.log(a.dim(i18n.t("notification:local.shortcuts.scanning")));
|
|
1723
|
-
const shortcuts = await listShortcuts();
|
|
1724
|
-
if (shortcuts.length === 0) {
|
|
1725
|
-
console.log(a.yellow(i18n.t("notification:local.shortcuts.noShortcuts")));
|
|
1726
|
-
console.log(a.dim(i18n.t("notification:local.shortcuts.createHint")));
|
|
1727
|
-
console.log("");
|
|
1728
|
-
return;
|
|
1729
|
-
}
|
|
1730
|
-
console.log(a.green(i18n.t("notification:local.shortcuts.found", { count: shortcuts.length })));
|
|
1731
|
-
console.log("");
|
|
1732
|
-
const { shortcutName } = await inquirer.prompt([{
|
|
1733
|
-
type: "list",
|
|
1734
|
-
name: "shortcutName",
|
|
1735
|
-
message: i18n.t("notification:local.shortcuts.selectShortcut"),
|
|
1736
|
-
choices: [
|
|
1737
|
-
...shortcuts.map((s) => ({ name: s, value: s })),
|
|
1738
|
-
{ name: i18n.t("notification:local.shortcuts.enterManually"), value: "__manual__" },
|
|
1739
|
-
{ name: i18n.t("notification:local.shortcuts.disable"), value: "__disable__" }
|
|
1740
|
-
]
|
|
1741
|
-
}]);
|
|
1742
|
-
if (shortcutName === "__disable__") {
|
|
1743
|
-
await saveLocalNotificationConfig({ shortcutName: "" });
|
|
1744
|
-
console.log(a.yellow(`\u23F8\uFE0F ${i18n.t("notification:local.shortcuts.disabled")}`));
|
|
1745
|
-
return;
|
|
1746
|
-
}
|
|
1747
|
-
let finalShortcutName = shortcutName;
|
|
1748
|
-
if (shortcutName === "__manual__") {
|
|
1749
|
-
const { manualName } = await inquirer.prompt([{
|
|
1750
|
-
type: "input",
|
|
1751
|
-
name: "manualName",
|
|
1752
|
-
message: i18n.t("notification:local.shortcuts.enterName"),
|
|
1753
|
-
validate: (value) => !!value.trim() || i18n.t("notification:errors.required")
|
|
1754
|
-
}]);
|
|
1755
|
-
finalShortcutName = manualName.trim();
|
|
1756
|
-
}
|
|
1757
|
-
await saveLocalNotificationConfig({ shortcutName: finalShortcutName });
|
|
1758
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:local.shortcuts.configured", { name: finalShortcutName })}`));
|
|
1759
|
-
const { shouldTest } = await inquirer.prompt([{
|
|
1760
|
-
type: "confirm",
|
|
1761
|
-
name: "shouldTest",
|
|
1762
|
-
message: i18n.t("notification:local.shortcuts.testNow"),
|
|
1763
|
-
default: true
|
|
1764
|
-
}]);
|
|
1765
|
-
if (shouldTest) {
|
|
1766
|
-
await testShortcutsNotification(finalShortcutName);
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
async function configureBark() {
|
|
1770
|
-
console.log("");
|
|
1771
|
-
console.log(a.bold.cyan(i18n.t("notification:local.bark.title")));
|
|
1772
|
-
console.log(a.dim(i18n.t("notification:local.bark.description")));
|
|
1773
|
-
console.log("");
|
|
1774
|
-
const config = await loadLocalNotificationConfig();
|
|
1775
|
-
const { action } = await inquirer.prompt([{
|
|
1776
|
-
type: "list",
|
|
1777
|
-
name: "action",
|
|
1778
|
-
message: i18n.t("notification:menu.selectAction"),
|
|
1779
|
-
choices: [
|
|
1780
|
-
{ name: i18n.t("notification:local.bark.configureServer"), value: "configure" },
|
|
1781
|
-
{ name: i18n.t("notification:local.bark.disable"), value: "disable" },
|
|
1782
|
-
{ name: `\u2190 ${i18n.t("notification:menu.back")}`, value: "back" }
|
|
1783
|
-
]
|
|
1784
|
-
}]);
|
|
1785
|
-
if (action === "back") {
|
|
1786
|
-
return;
|
|
1787
|
-
}
|
|
1788
|
-
if (action === "disable") {
|
|
1789
|
-
await saveLocalNotificationConfig({ barkUrl: "" });
|
|
1790
|
-
console.log(a.yellow(`\u23F8\uFE0F ${i18n.t("notification:local.bark.disabled")}`));
|
|
1791
|
-
return;
|
|
1792
|
-
}
|
|
1793
|
-
const { barkUrl } = await inquirer.prompt([{
|
|
1794
|
-
type: "input",
|
|
1795
|
-
name: "barkUrl",
|
|
1796
|
-
message: i18n.t("notification:local.bark.enterUrl"),
|
|
1797
|
-
default: config.barkUrl || "https://api.day.app/your-key",
|
|
1798
|
-
validate: (value) => {
|
|
1799
|
-
if (!isValidBarkUrl(value)) {
|
|
1800
|
-
return i18n.t("notification:local.bark.invalidUrl");
|
|
1801
|
-
}
|
|
1802
|
-
return true;
|
|
1803
|
-
}
|
|
1804
|
-
}]);
|
|
1805
|
-
await saveLocalNotificationConfig({ barkUrl });
|
|
1806
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:local.bark.configured")}`));
|
|
1807
|
-
const { shouldTest } = await inquirer.prompt([{
|
|
1808
|
-
type: "confirm",
|
|
1809
|
-
name: "shouldTest",
|
|
1810
|
-
message: i18n.t("notification:local.bark.testNow"),
|
|
1811
|
-
default: true
|
|
1812
|
-
}]);
|
|
1813
|
-
if (shouldTest) {
|
|
1814
|
-
await testBarkNotification(barkUrl);
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
async function testLocalNotification() {
|
|
1818
|
-
const config = await loadLocalNotificationConfig();
|
|
1819
|
-
console.log("");
|
|
1820
|
-
console.log(a.green(i18n.t("notification:local.testing")));
|
|
1821
|
-
console.log("");
|
|
1822
|
-
let hasAnyEnabled = false;
|
|
1823
|
-
if (config.shortcutName) {
|
|
1824
|
-
hasAnyEnabled = true;
|
|
1825
|
-
await testShortcutsNotification(config.shortcutName);
|
|
1826
|
-
}
|
|
1827
|
-
if (config.barkUrl) {
|
|
1828
|
-
hasAnyEnabled = true;
|
|
1829
|
-
await testBarkNotification(config.barkUrl);
|
|
1830
|
-
}
|
|
1831
|
-
if (!hasAnyEnabled) {
|
|
1832
|
-
console.log(a.yellow(i18n.t("notification:local.noLocalEnabled")));
|
|
1833
|
-
console.log(a.dim(i18n.t("notification:local.configureFirst")));
|
|
1834
|
-
}
|
|
1835
|
-
console.log("");
|
|
1836
|
-
}
|
|
1837
|
-
async function testShortcutsNotification(shortcutName) {
|
|
1838
|
-
console.log(a.dim(`${i18n.t("notification:local.shortcuts.testing", { name: shortcutName })}...`));
|
|
1839
|
-
try {
|
|
1840
|
-
const service = await getLocalNotificationService();
|
|
1841
|
-
await service.sendShortcutNotification(shortcutName, {
|
|
1842
|
-
title: i18n.t("notification:local.testTitle"),
|
|
1843
|
-
body: i18n.t("notification:local.testBody")
|
|
1844
|
-
});
|
|
1845
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:local.shortcuts.name")}: ${i18n.t("notification:test.success")}`));
|
|
1846
|
-
} catch (error) {
|
|
1847
|
-
console.log(a.red(`\u274C ${i18n.t("notification:local.shortcuts.name")}: ${error instanceof Error ? error.message : i18n.t("notification:test.failed")}`));
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
async function testBarkNotification(barkUrl) {
|
|
1851
|
-
console.log(a.dim(`${i18n.t("notification:local.bark.testing")}...`));
|
|
1852
|
-
try {
|
|
1853
|
-
const service = await getLocalNotificationService();
|
|
1854
|
-
await service.sendBarkNotification(barkUrl, {
|
|
1855
|
-
title: i18n.t("notification:local.testTitle"),
|
|
1856
|
-
body: i18n.t("notification:local.testBody")
|
|
1857
|
-
});
|
|
1858
|
-
console.log(a.green(`\u2705 ${i18n.t("notification:local.bark.name")}: ${i18n.t("notification:test.success")}`));
|
|
1859
|
-
} catch (error) {
|
|
1860
|
-
console.log(a.red(`\u274C ${i18n.t("notification:local.bark.name")}: ${error instanceof Error ? error.message : i18n.t("notification:test.failed")}`));
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
export { notificationCommand };
|