runwork 0.15.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +19272 -116
- package/package.json +4 -4
- package/dist/__tests__/curl-parser.test.d.ts +0 -1
- package/dist/__tests__/curl-parser.test.js +0 -250
- package/dist/__tests__/install-scripts.test.d.ts +0 -1
- package/dist/__tests__/install-scripts.test.js +0 -280
- package/dist/__tests__/types-manager.test.d.ts +0 -1
- package/dist/__tests__/types-manager.test.js +0 -89
- package/dist/agents/__tests__/chatgpt-registry.test.d.ts +0 -1
- package/dist/agents/__tests__/chatgpt-registry.test.js +0 -61
- package/dist/agents/__tests__/claude-code-managed-block.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-code-managed-block.test.js +0 -97
- package/dist/agents/__tests__/claude-code-plugin.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-code-plugin.test.js +0 -47
- package/dist/agents/__tests__/claude-code-stats.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-code-stats.test.js +0 -390
- package/dist/agents/__tests__/claude-desktop-memory.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-desktop-memory.test.js +0 -154
- package/dist/agents/__tests__/claude-desktop-plugin-tree.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-desktop-plugin-tree.test.js +0 -161
- package/dist/agents/__tests__/claude-desktop-plugin.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-desktop-plugin.test.js +0 -117
- package/dist/agents/__tests__/claude-desktop-rpm.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-desktop-rpm.test.js +0 -126
- package/dist/agents/__tests__/claude-desktop-stats.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-desktop-stats.test.js +0 -280
- package/dist/agents/__tests__/codex-instructions.test.d.ts +0 -1
- package/dist/agents/__tests__/codex-instructions.test.js +0 -57
- package/dist/agents/__tests__/codex-minimum-permissions.test.d.ts +0 -1
- package/dist/agents/__tests__/codex-minimum-permissions.test.js +0 -82
- package/dist/agents/__tests__/codex-stats.test.d.ts +0 -1
- package/dist/agents/__tests__/codex-stats.test.js +0 -370
- package/dist/agents/__tests__/cursor-merge.test.d.ts +0 -1
- package/dist/agents/__tests__/cursor-merge.test.js +0 -66
- package/dist/agents/__tests__/cursor-stats.test.d.ts +0 -1
- package/dist/agents/__tests__/cursor-stats.test.js +0 -183
- package/dist/agents/__tests__/defaults-merge.test.d.ts +0 -1
- package/dist/agents/__tests__/defaults-merge.test.js +0 -268
- package/dist/agents/__tests__/detection.test.d.ts +0 -1
- package/dist/agents/__tests__/detection.test.js +0 -89
- package/dist/agents/__tests__/graceful-degradation.test.d.ts +0 -1
- package/dist/agents/__tests__/graceful-degradation.test.js +0 -129
- package/dist/agents/__tests__/instruction-hint.test.d.ts +0 -1
- package/dist/agents/__tests__/instruction-hint.test.js +0 -69
- package/dist/agents/__tests__/intro-skill.test.d.ts +0 -1
- package/dist/agents/__tests__/intro-skill.test.js +0 -193
- package/dist/agents/__tests__/schema-validation.test.d.ts +0 -1
- package/dist/agents/__tests__/schema-validation.test.js +0 -212
- package/dist/agents/__tests__/skill-slug.test.d.ts +0 -1
- package/dist/agents/__tests__/skill-slug.test.js +0 -77
- package/dist/agents/claude-code.d.ts +0 -53
- package/dist/agents/claude-code.js +0 -754
- package/dist/agents/claude-desktop-plugin-tree.d.ts +0 -59
- package/dist/agents/claude-desktop-plugin-tree.js +0 -143
- package/dist/agents/claude-desktop.d.ts +0 -47
- package/dist/agents/claude-desktop.js +0 -684
- package/dist/agents/cline.d.ts +0 -15
- package/dist/agents/cline.js +0 -130
- package/dist/agents/codex.d.ts +0 -44
- package/dist/agents/codex.js +0 -453
- package/dist/agents/conversation-skills.d.ts +0 -18
- package/dist/agents/conversation-skills.js +0 -229
- package/dist/agents/cursor.d.ts +0 -48
- package/dist/agents/cursor.js +0 -324
- package/dist/agents/default-config.d.ts +0 -30
- package/dist/agents/default-config.js +0 -67
- package/dist/agents/defaults-merge.d.ts +0 -72
- package/dist/agents/defaults-merge.js +0 -131
- package/dist/agents/detect.d.ts +0 -4
- package/dist/agents/detect.js +0 -67
- package/dist/agents/detection.d.ts +0 -18
- package/dist/agents/detection.js +0 -94
- package/dist/agents/gemini.d.ts +0 -15
- package/dist/agents/gemini.js +0 -122
- package/dist/agents/generic-adapter.d.ts +0 -25
- package/dist/agents/generic-adapter.js +0 -151
- package/dist/agents/intro-skill.d.ts +0 -45
- package/dist/agents/intro-skill.js +0 -447
- package/dist/agents/registry-data.d.ts +0 -192
- package/dist/agents/registry-data.js +0 -669
- package/dist/agents/registry.d.ts +0 -13
- package/dist/agents/registry.js +0 -36
- package/dist/agents/runtime-detection.d.ts +0 -82
- package/dist/agents/runtime-detection.js +0 -271
- package/dist/agents/session-start-hook.d.ts +0 -37
- package/dist/agents/session-start-hook.js +0 -159
- package/dist/agents/types.d.ts +0 -171
- package/dist/agents/types.js +0 -75
- package/dist/agents/utils/instruction-hint.d.ts +0 -21
- package/dist/agents/utils/instruction-hint.js +0 -108
- package/dist/agents/utils/json-config.d.ts +0 -19
- package/dist/agents/utils/json-config.js +0 -69
- package/dist/agents/utils/toml-config.d.ts +0 -8
- package/dist/agents/utils/toml-config.js +0 -27
- package/dist/agents/windsurf.d.ts +0 -14
- package/dist/agents/windsurf.js +0 -102
- package/dist/api/__tests__/client.test.d.ts +0 -1
- package/dist/api/__tests__/client.test.js +0 -272
- package/dist/api/client.d.ts +0 -326
- package/dist/api/client.js +0 -427
- package/dist/auth/__tests__/login-flow.test.d.ts +0 -1
- package/dist/auth/__tests__/login-flow.test.js +0 -101
- package/dist/auth/__tests__/store.test.d.ts +0 -1
- package/dist/auth/__tests__/store.test.js +0 -132
- package/dist/auth/login-flow.d.ts +0 -26
- package/dist/auth/login-flow.js +0 -137
- package/dist/auth/store.d.ts +0 -5
- package/dist/auth/store.js +0 -62
- package/dist/commands/__tests__/clone-args.test.d.ts +0 -1
- package/dist/commands/__tests__/clone-args.test.js +0 -51
- package/dist/commands/__tests__/clone-source-result.test.d.ts +0 -1
- package/dist/commands/__tests__/clone-source-result.test.js +0 -45
- package/dist/commands/__tests__/info-merge.test.d.ts +0 -1
- package/dist/commands/__tests__/info-merge.test.js +0 -55
- package/dist/commands/__tests__/mcp-entries.test.d.ts +0 -1
- package/dist/commands/__tests__/mcp-entries.test.js +0 -48
- package/dist/commands/__tests__/setup-persona.test.d.ts +0 -1
- package/dist/commands/__tests__/setup-persona.test.js +0 -31
- package/dist/commands/__tests__/setup.test.d.ts +0 -1
- package/dist/commands/__tests__/setup.test.js +0 -81
- package/dist/commands/__tests__/sync-redetect.test.d.ts +0 -1
- package/dist/commands/__tests__/sync-redetect.test.js +0 -96
- package/dist/commands/__tests__/sync-telemetry.test.d.ts +0 -1
- package/dist/commands/__tests__/sync-telemetry.test.js +0 -281
- package/dist/commands/__tests__/upgrade.test.d.ts +0 -1
- package/dist/commands/__tests__/upgrade.test.js +0 -157
- package/dist/commands/agents.d.ts +0 -2
- package/dist/commands/agents.js +0 -160
- package/dist/commands/api-keys.d.ts +0 -2
- package/dist/commands/api-keys.js +0 -95
- package/dist/commands/apps.d.ts +0 -2
- package/dist/commands/apps.js +0 -82
- package/dist/commands/build-plugin.d.ts +0 -2
- package/dist/commands/build-plugin.js +0 -135
- package/dist/commands/clone.d.ts +0 -60
- package/dist/commands/clone.js +0 -271
- package/dist/commands/components.d.ts +0 -2
- package/dist/commands/components.js +0 -43
- package/dist/commands/deploy.d.ts +0 -2
- package/dist/commands/deploy.js +0 -279
- package/dist/commands/dev.d.ts +0 -11
- package/dist/commands/dev.js +0 -1011
- package/dist/commands/doctor.d.ts +0 -2
- package/dist/commands/doctor.js +0 -230
- package/dist/commands/endpoints.d.ts +0 -2
- package/dist/commands/endpoints.js +0 -189
- package/dist/commands/entities.d.ts +0 -2
- package/dist/commands/entities.js +0 -198
- package/dist/commands/files.d.ts +0 -2
- package/dist/commands/files.js +0 -193
- package/dist/commands/inbox.d.ts +0 -11
- package/dist/commands/inbox.js +0 -60
- package/dist/commands/info.d.ts +0 -178
- package/dist/commands/info.js +0 -455
- package/dist/commands/init.d.ts +0 -21
- package/dist/commands/init.js +0 -185
- package/dist/commands/integrations.d.ts +0 -2
- package/dist/commands/integrations.js +0 -233
- package/dist/commands/login.d.ts +0 -2
- package/dist/commands/login.js +0 -44
- package/dist/commands/logout.d.ts +0 -2
- package/dist/commands/logout.js +0 -13
- package/dist/commands/logs.d.ts +0 -2
- package/dist/commands/logs.js +0 -232
- package/dist/commands/mcp-entries.d.ts +0 -13
- package/dist/commands/mcp-entries.js +0 -26
- package/dist/commands/mcp.d.ts +0 -2
- package/dist/commands/mcp.js +0 -245
- package/dist/commands/open.d.ts +0 -2
- package/dist/commands/open.js +0 -53
- package/dist/commands/resume.d.ts +0 -2
- package/dist/commands/resume.js +0 -289
- package/dist/commands/save-convo.d.ts +0 -8
- package/dist/commands/save-convo.js +0 -20
- package/dist/commands/schedules.d.ts +0 -2
- package/dist/commands/schedules.js +0 -221
- package/dist/commands/setup.d.ts +0 -27
- package/dist/commands/setup.js +0 -177
- package/dist/commands/share-convo.d.ts +0 -24
- package/dist/commands/share-convo.js +0 -167
- package/dist/commands/skills.d.ts +0 -2
- package/dist/commands/skills.js +0 -352
- package/dist/commands/sync-telemetry.d.ts +0 -33
- package/dist/commands/sync-telemetry.js +0 -186
- package/dist/commands/sync.d.ts +0 -37
- package/dist/commands/sync.js +0 -702
- package/dist/commands/uninstall.d.ts +0 -2
- package/dist/commands/uninstall.js +0 -145
- package/dist/commands/upgrade.d.ts +0 -14
- package/dist/commands/upgrade.js +0 -217
- package/dist/commands/validate.d.ts +0 -2
- package/dist/commands/validate.js +0 -89
- package/dist/commands/welcome.d.ts +0 -1
- package/dist/commands/welcome.js +0 -86
- package/dist/commands/workflows.d.ts +0 -2
- package/dist/commands/workflows.js +0 -187
- package/dist/deploy/__tests__/deploy-state.test.d.ts +0 -1
- package/dist/deploy/__tests__/deploy-state.test.js +0 -80
- package/dist/deploy/__tests__/deploy-status.test.d.ts +0 -1
- package/dist/deploy/__tests__/deploy-status.test.js +0 -41
- package/dist/deploy/__tests__/detach-args.test.d.ts +0 -1
- package/dist/deploy/__tests__/detach-args.test.js +0 -30
- package/dist/deploy/deploy-state.d.ts +0 -36
- package/dist/deploy/deploy-state.js +0 -63
- package/dist/deploy/deploy-status.d.ts +0 -21
- package/dist/deploy/deploy-status.js +0 -36
- package/dist/deploy/detach.d.ts +0 -32
- package/dist/deploy/detach.js +0 -71
- package/dist/dev/__tests__/attach.test.d.ts +0 -1
- package/dist/dev/__tests__/attach.test.js +0 -296
- package/dist/dev/__tests__/detach.test.d.ts +0 -1
- package/dist/dev/__tests__/detach.test.js +0 -404
- package/dist/dev/__tests__/preview-url-poller.test.d.ts +0 -1
- package/dist/dev/__tests__/preview-url-poller.test.js +0 -149
- package/dist/dev/__tests__/session.test.d.ts +0 -1
- package/dist/dev/__tests__/session.test.js +0 -446
- package/dist/dev/__tests__/startup-sync.test.d.ts +0 -1
- package/dist/dev/__tests__/startup-sync.test.js +0 -32
- package/dist/dev/__tests__/stop.test.d.ts +0 -1
- package/dist/dev/__tests__/stop.test.js +0 -172
- package/dist/dev/attach.d.ts +0 -120
- package/dist/dev/attach.js +0 -269
- package/dist/dev/detach.d.ts +0 -187
- package/dist/dev/detach.js +0 -292
- package/dist/dev/preview-url-poller.d.ts +0 -35
- package/dist/dev/preview-url-poller.js +0 -50
- package/dist/dev/session.d.ts +0 -175
- package/dist/dev/session.js +0 -258
- package/dist/dev/startup-sync.d.ts +0 -23
- package/dist/dev/startup-sync.js +0 -14
- package/dist/dev/stop.d.ts +0 -52
- package/dist/dev/stop.js +0 -101
- package/dist/devtools/registry-data.d.ts +0 -46
- package/dist/devtools/registry-data.js +0 -40
- package/dist/generated/bundled-types.d.ts +0 -1
- package/dist/generated/bundled-types.js +0 -46
- package/dist/generated/version.d.ts +0 -1
- package/dist/generated/version.js +0 -2
- package/dist/git/__tests__/auto-commit.test.d.ts +0 -1
- package/dist/git/__tests__/auto-commit.test.js +0 -431
- package/dist/git/__tests__/classify-sync-error.test.d.ts +0 -1
- package/dist/git/__tests__/classify-sync-error.test.js +0 -57
- package/dist/git/__tests__/credential-helper-e2e.test.d.ts +0 -21
- package/dist/git/__tests__/credential-helper-e2e.test.js +0 -195
- package/dist/git/__tests__/credentials.test.d.ts +0 -1
- package/dist/git/__tests__/credentials.test.js +0 -141
- package/dist/git/__tests__/deploy-guard.test.d.ts +0 -1
- package/dist/git/__tests__/deploy-guard.test.js +0 -61
- package/dist/git/__tests__/ensure-git-credential-helper.test.d.ts +0 -1
- package/dist/git/__tests__/ensure-git-credential-helper.test.js +0 -133
- package/dist/git/__tests__/ensure-runwork-remote.test.d.ts +0 -1
- package/dist/git/__tests__/ensure-runwork-remote.test.js +0 -109
- package/dist/git/__tests__/identity.test.d.ts +0 -1
- package/dist/git/__tests__/identity.test.js +0 -146
- package/dist/git/__tests__/manifest.test.d.ts +0 -1
- package/dist/git/__tests__/manifest.test.js +0 -446
- package/dist/git/__tests__/preflight-resolution.test.d.ts +0 -1
- package/dist/git/__tests__/preflight-resolution.test.js +0 -366
- package/dist/git/__tests__/preflight.test.d.ts +0 -1
- package/dist/git/__tests__/preflight.test.js +0 -36
- package/dist/git/__tests__/repo-config.test.d.ts +0 -1
- package/dist/git/__tests__/repo-config.test.js +0 -26
- package/dist/git/__tests__/sync.test.d.ts +0 -1
- package/dist/git/__tests__/sync.test.js +0 -405
- package/dist/git/auto-commit.d.ts +0 -35
- package/dist/git/auto-commit.js +0 -293
- package/dist/git/classify-sync-error.d.ts +0 -21
- package/dist/git/classify-sync-error.js +0 -111
- package/dist/git/credentials.d.ts +0 -75
- package/dist/git/credentials.js +0 -164
- package/dist/git/critical-files.d.ts +0 -20
- package/dist/git/critical-files.js +0 -68
- package/dist/git/deploy-guard.d.ts +0 -53
- package/dist/git/deploy-guard.js +0 -78
- package/dist/git/identity.d.ts +0 -44
- package/dist/git/identity.js +0 -133
- package/dist/git/preflight.d.ts +0 -57
- package/dist/git/preflight.js +0 -276
- package/dist/git/remote.d.ts +0 -37
- package/dist/git/remote.js +0 -75
- package/dist/git/repo-config.d.ts +0 -8
- package/dist/git/repo-config.js +0 -29
- package/dist/git/sync.d.ts +0 -15
- package/dist/git/sync.js +0 -159
- package/dist/health/__tests__/checks.test.d.ts +0 -1
- package/dist/health/__tests__/checks.test.js +0 -357
- package/dist/health/__tests__/cli-distribution-checks.test.d.ts +0 -1
- package/dist/health/__tests__/cli-distribution-checks.test.js +0 -196
- package/dist/health/__tests__/fix.test.d.ts +0 -1
- package/dist/health/__tests__/fix.test.js +0 -72
- package/dist/health/__tests__/runner-filter.test.d.ts +0 -1
- package/dist/health/__tests__/runner-filter.test.js +0 -61
- package/dist/health/checks.d.ts +0 -42
- package/dist/health/checks.js +0 -636
- package/dist/health/fix.d.ts +0 -22
- package/dist/health/fix.js +0 -62
- package/dist/health/runner.d.ts +0 -8
- package/dist/health/runner.js +0 -47
- package/dist/health/types.d.ts +0 -12
- package/dist/health/types.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/logs/__tests__/tailer-format.test.d.ts +0 -1
- package/dist/logs/__tests__/tailer-format.test.js +0 -43
- package/dist/logs/tailer.d.ts +0 -17
- package/dist/logs/tailer.js +0 -217
- package/dist/sync/__tests__/change-detect.test.d.ts +0 -1
- package/dist/sync/__tests__/change-detect.test.js +0 -123
- package/dist/sync/__tests__/hash.test.d.ts +0 -1
- package/dist/sync/__tests__/hash.test.js +0 -18
- package/dist/sync/change-detect.d.ts +0 -19
- package/dist/sync/change-detect.js +0 -136
- package/dist/sync/conflict-ui.d.ts +0 -5
- package/dist/sync/conflict-ui.js +0 -78
- package/dist/sync/executor.d.ts +0 -23
- package/dist/sync/executor.js +0 -102
- package/dist/sync/hash.d.ts +0 -1
- package/dist/sync/hash.js +0 -4
- package/dist/sync/types.d.ts +0 -24
- package/dist/sync/types.js +0 -1
- package/dist/template/manifest.d.ts +0 -17
- package/dist/template/manifest.js +0 -145
- package/dist/tools/types.d.ts +0 -52
- package/dist/tools/types.js +0 -8
- package/dist/types-manager.d.ts +0 -1
- package/dist/types-manager.js +0 -35
- package/dist/types.d.ts +0 -349
- package/dist/types.js +0 -1
- package/dist/ui/__tests__/banner.test.d.ts +0 -1
- package/dist/ui/__tests__/banner.test.js +0 -98
- package/dist/ui/__tests__/colors.test.d.ts +0 -1
- package/dist/ui/__tests__/colors.test.js +0 -22
- package/dist/ui/__tests__/keyboard.test.d.ts +0 -1
- package/dist/ui/__tests__/keyboard.test.js +0 -34
- package/dist/ui/__tests__/status-line.test.d.ts +0 -1
- package/dist/ui/__tests__/status-line.test.js +0 -54
- package/dist/ui/banner.d.ts +0 -29
- package/dist/ui/banner.js +0 -153
- package/dist/ui/colors.d.ts +0 -4
- package/dist/ui/colors.js +0 -7
- package/dist/ui/keyboard.d.ts +0 -12
- package/dist/ui/keyboard.js +0 -61
- package/dist/ui/status-line.d.ts +0 -6
- package/dist/ui/status-line.js +0 -53
- package/dist/utils/__tests__/format-error.test.d.ts +0 -1
- package/dist/utils/__tests__/format-error.test.js +0 -43
- package/dist/utils/__tests__/help-json.test.d.ts +0 -1
- package/dist/utils/__tests__/help-json.test.js +0 -39
- package/dist/utils/__tests__/http.test.d.ts +0 -1
- package/dist/utils/__tests__/http.test.js +0 -381
- package/dist/utils/__tests__/ignore-matcher.test.d.ts +0 -1
- package/dist/utils/__tests__/ignore-matcher.test.js +0 -205
- package/dist/utils/__tests__/output.test.d.ts +0 -1
- package/dist/utils/__tests__/output.test.js +0 -38
- package/dist/utils/__tests__/prompt.test.d.ts +0 -1
- package/dist/utils/__tests__/prompt.test.js +0 -27
- package/dist/utils/__tests__/resolve.test.d.ts +0 -1
- package/dist/utils/__tests__/resolve.test.js +0 -95
- package/dist/utils/__tests__/sqlite.test.d.ts +0 -1
- package/dist/utils/__tests__/sqlite.test.js +0 -89
- package/dist/utils/agent-guidance.d.ts +0 -47
- package/dist/utils/agent-guidance.js +0 -128
- package/dist/utils/app-info.d.ts +0 -70
- package/dist/utils/app-info.js +0 -167
- package/dist/utils/curl-parser.d.ts +0 -12
- package/dist/utils/curl-parser.js +0 -162
- package/dist/utils/data-input.d.ts +0 -1
- package/dist/utils/data-input.js +0 -38
- package/dist/utils/format-error.d.ts +0 -10
- package/dist/utils/format-error.js +0 -38
- package/dist/utils/fs.d.ts +0 -5
- package/dist/utils/fs.js +0 -23
- package/dist/utils/help-json.d.ts +0 -70
- package/dist/utils/help-json.js +0 -88
- package/dist/utils/http.d.ts +0 -46
- package/dist/utils/http.js +0 -421
- package/dist/utils/ignore-matcher.d.ts +0 -47
- package/dist/utils/ignore-matcher.js +0 -132
- package/dist/utils/output.d.ts +0 -17
- package/dist/utils/output.js +0 -27
- package/dist/utils/prompt.d.ts +0 -5
- package/dist/utils/prompt.js +0 -42
- package/dist/utils/resolve.d.ts +0 -12
- package/dist/utils/resolve.js +0 -52
- package/dist/utils/setup-state.d.ts +0 -3
- package/dist/utils/setup-state.js +0 -19
- package/dist/utils/sqlite-adapter.d.ts +0 -48
- package/dist/utils/sqlite-adapter.js +0 -147
- package/dist/utils/sqlite.d.ts +0 -14
- package/dist/utils/sqlite.js +0 -14
- package/dist/utils/subprocess.d.ts +0 -19
- package/dist/utils/subprocess.js +0 -27
- package/dist/utils/which.d.ts +0 -14
- package/dist/utils/which.js +0 -47
- package/dist/utils/zip.d.ts +0 -7
- package/dist/utils/zip.js +0 -42
- package/dist/validate/__tests__/validate.test.d.ts +0 -1
- package/dist/validate/__tests__/validate.test.js +0 -42
- package/dist/validate/freshness.d.ts +0 -28
- package/dist/validate/freshness.js +0 -48
- package/dist/validate/preview.d.ts +0 -21
- package/dist/validate/preview.js +0 -39
- package/dist/workspace/resolve.d.ts +0 -19
- package/dist/workspace/resolve.js +0 -100
|
@@ -1,754 +0,0 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { homedir, platform } from 'os';
|
|
4
|
-
import { execFileSync } from '../utils/subprocess.js';
|
|
5
|
-
import { whichBinary } from '../utils/which.js';
|
|
6
|
-
import { buildSkillMd } from './types.js';
|
|
7
|
-
import { mergeJsonMcpServers, readJsonConfig, writeJsonConfig, removeRunworkMcpServers } from './utils/json-config.js';
|
|
8
|
-
import { writeHintToFile, writeTeamInstructionsToFile, removeHintFromFile, removeTeamInstructionsFromFile } from './utils/instruction-hint.js';
|
|
9
|
-
import { SESSION_START_HOOK_SCRIPT } from './session-start-hook.js';
|
|
10
|
-
const PLUGIN_NAME = 'runwork';
|
|
11
|
-
const PLUGIN_VERSION = '1.0.0';
|
|
12
|
-
function getPluginJson() {
|
|
13
|
-
return {
|
|
14
|
-
name: PLUGIN_NAME,
|
|
15
|
-
version: PLUGIN_VERSION,
|
|
16
|
-
description: 'Skills and tools from your Runwork workspace',
|
|
17
|
-
author: { name: 'Runwork' },
|
|
18
|
-
homepage: 'https://www.runwork.ai',
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
function getMarketplaceJson() {
|
|
22
|
-
return {
|
|
23
|
-
name: 'runwork',
|
|
24
|
-
owner: { name: 'Runwork' },
|
|
25
|
-
plugins: [
|
|
26
|
-
{
|
|
27
|
-
name: PLUGIN_NAME,
|
|
28
|
-
source: `./plugins/${PLUGIN_NAME}`,
|
|
29
|
-
description: 'Skills and tools from your Runwork workspace',
|
|
30
|
-
},
|
|
31
|
-
],
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
export class ClaudeCodeAdapter {
|
|
35
|
-
name = 'Claude Code';
|
|
36
|
-
slug = 'claude-code';
|
|
37
|
-
mcpProvidesSkills = true;
|
|
38
|
-
async detect() {
|
|
39
|
-
if (whichBinary('claude'))
|
|
40
|
-
return true;
|
|
41
|
-
// Fallback: check app bundle on macOS
|
|
42
|
-
if (platform() === 'darwin')
|
|
43
|
-
return existsSync('/Applications/Claude.app');
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
supportsMcpScope(_scope) {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
supportsSkills() {
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
async writeMcpServers(servers, scope) {
|
|
53
|
-
if (scope === 'project') {
|
|
54
|
-
// Project scope: write to .mcp.json (standard Claude Code location)
|
|
55
|
-
const filePath = join(process.cwd(), '.mcp.json');
|
|
56
|
-
const entries = {};
|
|
57
|
-
for (const s of servers) {
|
|
58
|
-
entries[s.name] = {
|
|
59
|
-
type: 'http',
|
|
60
|
-
url: s.url,
|
|
61
|
-
...(s.headers ? { headers: s.headers } : {}),
|
|
62
|
-
...(s.description ? { description: s.description } : {}),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
mergeJsonMcpServers(filePath, entries, 'mcpServers');
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
// User scope: write MCP config both in global settings and inside the plugin
|
|
69
|
-
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
70
|
-
const entries = {};
|
|
71
|
-
for (const s of servers) {
|
|
72
|
-
entries[s.name] = {
|
|
73
|
-
type: 'http',
|
|
74
|
-
url: s.url,
|
|
75
|
-
...(s.headers ? { headers: s.headers } : {}),
|
|
76
|
-
...(s.description ? { description: s.description } : {}),
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
mergeJsonMcpServers(settingsPath, entries, 'mcpServers');
|
|
80
|
-
// Also write .mcp.json in the plugin directory
|
|
81
|
-
const pluginDir = this.getPluginDir();
|
|
82
|
-
mkdirSync(pluginDir, { recursive: true });
|
|
83
|
-
const pluginMcpPath = join(pluginDir, '.mcp.json');
|
|
84
|
-
const marketDir = this.getMarketplaceDir();
|
|
85
|
-
mkdirSync(marketDir, { recursive: true });
|
|
86
|
-
const marketplaceMcpPath = join(marketDir, '.mcp.json');
|
|
87
|
-
const pluginEntries = {};
|
|
88
|
-
for (const s of servers) {
|
|
89
|
-
pluginEntries[s.name] = {
|
|
90
|
-
type: 'http',
|
|
91
|
-
url: s.url,
|
|
92
|
-
...(s.headers ? { headers: s.headers } : {}),
|
|
93
|
-
...(s.description ? { description: s.description } : {}),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
writeJsonConfig(pluginMcpPath, pluginEntries);
|
|
97
|
-
writeJsonConfig(marketplaceMcpPath, pluginEntries);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Install the Runwork SessionStart hook into the plugin tree (user scope).
|
|
102
|
-
* The hook writes ~/.runwork/sessions/<sessionId>.json with the active
|
|
103
|
-
* conversation's transcript path + metadata, used by `runwork share-convo`
|
|
104
|
-
* for high-fidelity capture and by `runwork resume` for placement.
|
|
105
|
-
*
|
|
106
|
-
* Called as part of user-scope writeSkills() to keep sync flow consistent.
|
|
107
|
-
* Emits a one-line confirmation matching the format other adapter steps
|
|
108
|
-
* use (` [Claude Code] Installed ... (user)`) so `runwork sync` output
|
|
109
|
-
* shows whether the hook actually landed.
|
|
110
|
-
*/
|
|
111
|
-
installSessionStartHook(pluginDir, label) {
|
|
112
|
-
const hooksDir = join(pluginDir, 'hooks');
|
|
113
|
-
mkdirSync(hooksDir, { recursive: true });
|
|
114
|
-
const scriptPath = join(hooksDir, 'on-session-start.sh');
|
|
115
|
-
writeFileSync(scriptPath, SESSION_START_HOOK_SCRIPT);
|
|
116
|
-
try {
|
|
117
|
-
// Best-effort executable bit; chmod is a no-op on Windows.
|
|
118
|
-
chmodSync(scriptPath, 0o755);
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
/* not fatal */
|
|
122
|
-
}
|
|
123
|
-
const hooksManifest = {
|
|
124
|
-
SessionStart: [
|
|
125
|
-
{ command: '${CLAUDE_PLUGIN_ROOT}/hooks/on-session-start.sh' },
|
|
126
|
-
],
|
|
127
|
-
};
|
|
128
|
-
writeFileSync(join(hooksDir, 'hooks.json'), JSON.stringify(hooksManifest, null, 2));
|
|
129
|
-
console.log(` [Claude Code] Installed SessionStart hook (${label}) -> ${scriptPath}`);
|
|
130
|
-
}
|
|
131
|
-
async writeSkills(skills, scope) {
|
|
132
|
-
// Always write to the standard skills directory (guaranteed to work)
|
|
133
|
-
const baseDir = scope === 'project'
|
|
134
|
-
? join(process.cwd(), '.claude', 'skills')
|
|
135
|
-
: join(homedir(), '.claude', 'skills');
|
|
136
|
-
for (const skill of skills) {
|
|
137
|
-
const skillDir = join(baseDir, skill.filename);
|
|
138
|
-
cleanupOldSkillDir(baseDir, skill);
|
|
139
|
-
mkdirSync(skillDir, { recursive: true });
|
|
140
|
-
writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
|
|
141
|
-
}
|
|
142
|
-
// User scope: also write as a plugin for native plugin UI support
|
|
143
|
-
if (scope === 'user') {
|
|
144
|
-
const pluginDir = this.getPluginDir();
|
|
145
|
-
// Create plugin.json
|
|
146
|
-
const pluginJsonDir = join(pluginDir, '.claude-plugin');
|
|
147
|
-
mkdirSync(pluginJsonDir, { recursive: true });
|
|
148
|
-
writeFileSync(join(pluginJsonDir, 'plugin.json'), JSON.stringify(getPluginJson(), null, 2));
|
|
149
|
-
// Write skill files inside the plugin
|
|
150
|
-
for (const skill of skills) {
|
|
151
|
-
const skillDir = join(pluginDir, 'skills', skill.filename);
|
|
152
|
-
cleanupOldSkillDir(join(pluginDir, 'skills'), skill);
|
|
153
|
-
mkdirSync(skillDir, { recursive: true });
|
|
154
|
-
writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
|
|
155
|
-
}
|
|
156
|
-
// Also write to the marketplace directory
|
|
157
|
-
const marketDir = this.getMarketplaceDir();
|
|
158
|
-
const marketPluginJsonDir = join(marketDir, '.claude-plugin');
|
|
159
|
-
mkdirSync(marketPluginJsonDir, { recursive: true });
|
|
160
|
-
writeFileSync(join(marketPluginJsonDir, 'plugin.json'), JSON.stringify(getPluginJson(), null, 2));
|
|
161
|
-
for (const skill of skills) {
|
|
162
|
-
const skillDir = join(marketDir, 'skills', skill.filename);
|
|
163
|
-
cleanupOldSkillDir(join(marketDir, 'skills'), skill);
|
|
164
|
-
mkdirSync(skillDir, { recursive: true });
|
|
165
|
-
writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
|
|
166
|
-
}
|
|
167
|
-
// Register in installed_plugins.json
|
|
168
|
-
this.registerPlugin(pluginDir);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Install the runwork SessionStart hook for Claude Code (user scope only).
|
|
173
|
-
* Called once per sync by the main sync loop AFTER writeSkills has
|
|
174
|
-
* populated the plugin directories. Kept separate from writeSkills because
|
|
175
|
-
* writeSkills is also invoked per-skill by the diff executor during
|
|
176
|
-
* push/pull, and the hook install + log line must not repeat for every
|
|
177
|
-
* skill change.
|
|
178
|
-
*/
|
|
179
|
-
async writeBuiltInHooks(scope) {
|
|
180
|
-
if (scope !== 'user')
|
|
181
|
-
return;
|
|
182
|
-
const pluginDir = this.getPluginDir();
|
|
183
|
-
const marketDir = this.getMarketplaceDir();
|
|
184
|
-
if (existsSync(pluginDir)) {
|
|
185
|
-
this.installSessionStartHook(pluginDir, 'plugin cache');
|
|
186
|
-
}
|
|
187
|
-
if (existsSync(marketDir)) {
|
|
188
|
-
this.installSessionStartHook(marketDir, 'marketplace');
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
async writeInstructionHint(hint, scope) {
|
|
192
|
-
const filePath = scope === 'project'
|
|
193
|
-
? join(process.cwd(), '.claude', 'CLAUDE.md')
|
|
194
|
-
: join(homedir(), '.claude', 'CLAUDE.md');
|
|
195
|
-
writeHintToFile(filePath, hint);
|
|
196
|
-
}
|
|
197
|
-
async writeTeamInstructions(instructions, scope) {
|
|
198
|
-
// Write to CLAUDE.md via markers (direct instruction file approach)
|
|
199
|
-
const filePath = scope === 'project'
|
|
200
|
-
? join(process.cwd(), '.claude', 'CLAUDE.md')
|
|
201
|
-
: join(homedir(), '.claude', 'CLAUDE.md');
|
|
202
|
-
writeTeamInstructionsToFile(filePath, instructions);
|
|
203
|
-
// Also write as a plugin skill (same mechanism as Claude Desktop)
|
|
204
|
-
if (scope === 'user') {
|
|
205
|
-
const skillContent = `---\nname: runwork-team-instructions\ndescription: Team instructions from your Runwork workspace. Always follow these guidelines.\n---\n\n${instructions}`;
|
|
206
|
-
const pluginDir = this.getPluginDir();
|
|
207
|
-
const marketDir = this.getMarketplaceDir();
|
|
208
|
-
for (const dir of [
|
|
209
|
-
join(pluginDir, 'skills', 'runwork-team-instructions'),
|
|
210
|
-
join(marketDir, 'skills', 'runwork-team-instructions'),
|
|
211
|
-
]) {
|
|
212
|
-
mkdirSync(dir, { recursive: true });
|
|
213
|
-
writeFileSync(join(dir, 'SKILL.md'), skillContent);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
// ── Agent config (model, permissions) ──────────────────────────────
|
|
218
|
-
async writeAgentConfig(config, scope) {
|
|
219
|
-
const settingsPath = scope === 'project'
|
|
220
|
-
? join(process.cwd(), '.claude', 'settings.json')
|
|
221
|
-
: join(homedir(), '.claude', 'settings.json');
|
|
222
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
223
|
-
let settings = {};
|
|
224
|
-
if (existsSync(settingsPath)) {
|
|
225
|
-
try {
|
|
226
|
-
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
227
|
-
}
|
|
228
|
-
catch { /* start fresh */ }
|
|
229
|
-
}
|
|
230
|
-
if (config.modelPreference) {
|
|
231
|
-
settings.model = config.modelPreference;
|
|
232
|
-
}
|
|
233
|
-
if (config.permissionRules) {
|
|
234
|
-
if (!settings.permissions)
|
|
235
|
-
settings.permissions = {};
|
|
236
|
-
const perms = settings.permissions;
|
|
237
|
-
const MANAGED_MARKER = '# runwork-managed';
|
|
238
|
-
if (config.permissionRules.allow) {
|
|
239
|
-
const cleanExisting = this.removeAfterMarker(perms.allow ?? [], MANAGED_MARKER);
|
|
240
|
-
perms.allow = [...cleanExisting, MANAGED_MARKER, ...config.permissionRules.allow];
|
|
241
|
-
}
|
|
242
|
-
if (config.permissionRules.deny) {
|
|
243
|
-
const cleanExisting = this.removeAfterMarker(perms.deny ?? [], MANAGED_MARKER);
|
|
244
|
-
perms.deny = [...cleanExisting, MANAGED_MARKER, ...config.permissionRules.deny];
|
|
245
|
-
}
|
|
246
|
-
if (config.permissionRules.defaultMode) {
|
|
247
|
-
perms.defaultMode = config.permissionRules.defaultMode;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
mkdirSync(join(settingsPath, '..'), { recursive: true });
|
|
251
|
-
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
252
|
-
}
|
|
253
|
-
/** Remove the managed marker and all entries after it from a permissions array */
|
|
254
|
-
removeAfterMarker(arr, marker) {
|
|
255
|
-
const idx = arr.indexOf(marker);
|
|
256
|
-
return idx >= 0 ? arr.slice(0, idx) : [...arr];
|
|
257
|
-
}
|
|
258
|
-
/** Return entries after the managed marker for both allow and deny lists. */
|
|
259
|
-
async readManagedBlock(scope) {
|
|
260
|
-
const settingsPath = scope === 'project'
|
|
261
|
-
? join(process.cwd(), '.claude', 'settings.json')
|
|
262
|
-
: join(homedir(), '.claude', 'settings.json');
|
|
263
|
-
if (!existsSync(settingsPath))
|
|
264
|
-
return undefined;
|
|
265
|
-
const MANAGED_MARKER = '# runwork-managed';
|
|
266
|
-
try {
|
|
267
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
268
|
-
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
269
|
-
const perms = settings?.permissions;
|
|
270
|
-
if (!perms || typeof perms !== 'object')
|
|
271
|
-
return { allow: [], deny: [] };
|
|
272
|
-
const extractAfter = (arr) => {
|
|
273
|
-
if (!Array.isArray(arr))
|
|
274
|
-
return [];
|
|
275
|
-
const idx = arr.indexOf(MANAGED_MARKER);
|
|
276
|
-
return idx >= 0 ? arr.slice(idx + 1).filter((x) => typeof x === 'string') : [];
|
|
277
|
-
};
|
|
278
|
-
return {
|
|
279
|
-
allow: extractAfter(perms.allow),
|
|
280
|
-
deny: extractAfter(perms.deny),
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
catch {
|
|
284
|
-
return undefined;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
// ── Cleanup (uninstall) ────────────────────────────────────────────
|
|
288
|
-
async cleanup(scope, manifest) {
|
|
289
|
-
// 1. Remove MCP server entries
|
|
290
|
-
if (scope === 'project') {
|
|
291
|
-
removeRunworkMcpServers(join(process.cwd(), '.mcp.json'), 'mcpServers');
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
removeRunworkMcpServers(join(homedir(), '.claude', 'settings.json'), 'mcpServers');
|
|
295
|
-
}
|
|
296
|
-
// 2. Remove only skill directories that we synced (listed in manifest)
|
|
297
|
-
const skillsDir = scope === 'project'
|
|
298
|
-
? join(process.cwd(), '.claude', 'skills')
|
|
299
|
-
: join(homedir(), '.claude', 'skills');
|
|
300
|
-
if (existsSync(skillsDir) && manifest?.skillFilenames.length) {
|
|
301
|
-
const allowed = new Set(manifest.skillFilenames);
|
|
302
|
-
for (const entry of readdirSync(skillsDir)) {
|
|
303
|
-
if (!allowed.has(entry))
|
|
304
|
-
continue;
|
|
305
|
-
try {
|
|
306
|
-
rmSync(join(skillsDir, entry), { recursive: true, force: true });
|
|
307
|
-
}
|
|
308
|
-
catch { /* best-effort */ }
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
// 3. Remove instruction hints and team instructions
|
|
312
|
-
const instructionFile = scope === 'project'
|
|
313
|
-
? join(process.cwd(), '.claude', 'CLAUDE.md')
|
|
314
|
-
: join(homedir(), '.claude', 'CLAUDE.md');
|
|
315
|
-
removeHintFromFile(instructionFile);
|
|
316
|
-
removeTeamInstructionsFromFile(instructionFile);
|
|
317
|
-
// 4. Remove agent config overrides (managed permission entries, model, enabledPlugins)
|
|
318
|
-
if (scope === 'user') {
|
|
319
|
-
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
320
|
-
if (existsSync(settingsPath)) {
|
|
321
|
-
try {
|
|
322
|
-
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
323
|
-
// Remove managed permission entries
|
|
324
|
-
if (settings.permissions) {
|
|
325
|
-
const MANAGED_MARKER = '# runwork-managed';
|
|
326
|
-
if (settings.permissions.allow) {
|
|
327
|
-
const idx = settings.permissions.allow.indexOf(MANAGED_MARKER);
|
|
328
|
-
if (idx >= 0)
|
|
329
|
-
settings.permissions.allow = settings.permissions.allow.slice(0, idx);
|
|
330
|
-
}
|
|
331
|
-
if (settings.permissions.deny) {
|
|
332
|
-
const idx = settings.permissions.deny.indexOf(MANAGED_MARKER);
|
|
333
|
-
if (idx >= 0)
|
|
334
|
-
settings.permissions.deny = settings.permissions.deny.slice(0, idx);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
// Remove plugin enablement
|
|
338
|
-
if (settings.enabledPlugins) {
|
|
339
|
-
delete settings.enabledPlugins[`${PLUGIN_NAME}@runwork`];
|
|
340
|
-
}
|
|
341
|
-
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
342
|
-
}
|
|
343
|
-
catch { /* best-effort */ }
|
|
344
|
-
}
|
|
345
|
-
// 5. Remove plugin directories
|
|
346
|
-
const pluginDir = this.getPluginDir();
|
|
347
|
-
const marketRoot = this.getMarketplaceRoot();
|
|
348
|
-
for (const dir of [pluginDir, marketRoot]) {
|
|
349
|
-
if (existsSync(dir)) {
|
|
350
|
-
try {
|
|
351
|
-
rmSync(dir, { recursive: true, force: true });
|
|
352
|
-
}
|
|
353
|
-
catch { /* best-effort */ }
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
// 6. Remove from installed_plugins.json
|
|
357
|
-
const pluginsBase = this.getPluginsBaseDir();
|
|
358
|
-
const installedPath = join(pluginsBase, 'installed_plugins.json');
|
|
359
|
-
if (existsSync(installedPath)) {
|
|
360
|
-
try {
|
|
361
|
-
const installed = readJsonConfig(installedPath);
|
|
362
|
-
if (installed.plugins) {
|
|
363
|
-
delete installed.plugins[`${PLUGIN_NAME}@runwork`];
|
|
364
|
-
writeJsonConfig(installedPath, installed);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
catch { /* best-effort */ }
|
|
368
|
-
}
|
|
369
|
-
// 7. Remove from known_marketplaces.json
|
|
370
|
-
const marketplacesPath = join(pluginsBase, 'known_marketplaces.json');
|
|
371
|
-
if (existsSync(marketplacesPath)) {
|
|
372
|
-
try {
|
|
373
|
-
const marketplaces = readJsonConfig(marketplacesPath);
|
|
374
|
-
delete marketplaces['runwork'];
|
|
375
|
-
writeJsonConfig(marketplacesPath, marketplaces);
|
|
376
|
-
}
|
|
377
|
-
catch { /* best-effort */ }
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
// ── Usage stats (passive, best-effort) ─────────────────────────────
|
|
382
|
-
async readUsageStats(lastSyncAt) {
|
|
383
|
-
// Source of truth: ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl
|
|
384
|
-
// Each line is one user/assistant/system entry. The file grows continuously
|
|
385
|
-
// during a live session, so mtime + per-entry timestamp give an accurate
|
|
386
|
-
// post-sync activity picture. (stats-cache.json was abandoned upstream and
|
|
387
|
-
// session-env/ only records session-start hooks.)
|
|
388
|
-
try {
|
|
389
|
-
const claudeDir = join(homedir(), '.claude');
|
|
390
|
-
if (!existsSync(claudeDir))
|
|
391
|
-
return null;
|
|
392
|
-
const projectsDir = join(claudeDir, 'projects');
|
|
393
|
-
if (!existsSync(projectsDir))
|
|
394
|
-
return null;
|
|
395
|
-
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
396
|
-
let sessionCount = 0;
|
|
397
|
-
let messageCount = 0;
|
|
398
|
-
let toolCallCount = 0;
|
|
399
|
-
let inputTokens = 0;
|
|
400
|
-
let outputTokens = 0;
|
|
401
|
-
let cacheReadTokens = 0;
|
|
402
|
-
let latestActivityMs = 0;
|
|
403
|
-
// Dedup: ccusage uses ${message.id}:${requestId} to drop retransmissions.
|
|
404
|
-
// Entries without both fields pass through undeduped (same behavior).
|
|
405
|
-
const seenEntries = new Set();
|
|
406
|
-
let cwdEntries;
|
|
407
|
-
try {
|
|
408
|
-
cwdEntries = readdirSync(projectsDir);
|
|
409
|
-
}
|
|
410
|
-
catch {
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
for (const cwd of cwdEntries) {
|
|
414
|
-
const cwdPath = join(projectsDir, cwd);
|
|
415
|
-
let files;
|
|
416
|
-
try {
|
|
417
|
-
files = readdirSync(cwdPath);
|
|
418
|
-
}
|
|
419
|
-
catch {
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
for (const file of files) {
|
|
423
|
-
if (!file.endsWith('.jsonl'))
|
|
424
|
-
continue;
|
|
425
|
-
const filePath = join(cwdPath, file);
|
|
426
|
-
let stat;
|
|
427
|
-
try {
|
|
428
|
-
stat = statSync(filePath);
|
|
429
|
-
}
|
|
430
|
-
catch {
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
if (stat.mtimeMs <= sinceMs)
|
|
434
|
-
continue;
|
|
435
|
-
let content;
|
|
436
|
-
try {
|
|
437
|
-
content = readFileSync(filePath, 'utf-8');
|
|
438
|
-
}
|
|
439
|
-
catch {
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
let sessionHadActivity = false;
|
|
443
|
-
for (const line of content.split('\n')) {
|
|
444
|
-
if (!line)
|
|
445
|
-
continue;
|
|
446
|
-
let entry;
|
|
447
|
-
try {
|
|
448
|
-
entry = JSON.parse(line);
|
|
449
|
-
}
|
|
450
|
-
catch {
|
|
451
|
-
continue;
|
|
452
|
-
}
|
|
453
|
-
const tsRaw = entry.timestamp;
|
|
454
|
-
if (typeof tsRaw !== 'string')
|
|
455
|
-
continue;
|
|
456
|
-
const ts = Date.parse(tsRaw);
|
|
457
|
-
if (!Number.isFinite(ts) || ts <= sinceMs)
|
|
458
|
-
continue;
|
|
459
|
-
sessionHadActivity = true;
|
|
460
|
-
if (ts > latestActivityMs)
|
|
461
|
-
latestActivityMs = ts;
|
|
462
|
-
const type = entry.type;
|
|
463
|
-
if (type === 'user' || type === 'assistant') {
|
|
464
|
-
// Dedup by message.id + requestId (same approach as ccusage)
|
|
465
|
-
const msg = entry.message;
|
|
466
|
-
const msgId = msg && typeof msg === 'object' ? msg.id : undefined;
|
|
467
|
-
const reqId = entry.requestId;
|
|
468
|
-
if (typeof msgId === 'string' && typeof reqId === 'string') {
|
|
469
|
-
const dedupKey = `${msgId}:${reqId}`;
|
|
470
|
-
if (seenEntries.has(dedupKey))
|
|
471
|
-
continue;
|
|
472
|
-
seenEntries.add(dedupKey);
|
|
473
|
-
}
|
|
474
|
-
messageCount++;
|
|
475
|
-
}
|
|
476
|
-
if (type === 'assistant') {
|
|
477
|
-
const msg = entry.message;
|
|
478
|
-
if (msg && typeof msg === 'object') {
|
|
479
|
-
const msgRecord = msg;
|
|
480
|
-
const blocks = Array.isArray(msgRecord.content) ? msgRecord.content : [];
|
|
481
|
-
for (const block of blocks) {
|
|
482
|
-
if (block && typeof block === 'object'
|
|
483
|
-
&& block.type === 'tool_use') {
|
|
484
|
-
toolCallCount++;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
const usage = msgRecord.usage;
|
|
488
|
-
if (usage && typeof usage === 'object') {
|
|
489
|
-
const u = usage;
|
|
490
|
-
if (typeof u.input_tokens === 'number')
|
|
491
|
-
inputTokens += u.input_tokens;
|
|
492
|
-
if (typeof u.output_tokens === 'number')
|
|
493
|
-
outputTokens += u.output_tokens;
|
|
494
|
-
if (typeof u.cache_read_input_tokens === 'number') {
|
|
495
|
-
cacheReadTokens += u.cache_read_input_tokens;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
if (sessionHadActivity)
|
|
502
|
-
sessionCount++;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
if (sessionCount === 0)
|
|
506
|
-
return null;
|
|
507
|
-
return {
|
|
508
|
-
hasNewActivity: true,
|
|
509
|
-
sessionCount,
|
|
510
|
-
messageCount,
|
|
511
|
-
toolCallCount,
|
|
512
|
-
tokensUsed: inputTokens + outputTokens,
|
|
513
|
-
aiLinesAdded: 0,
|
|
514
|
-
aiLinesRemoved: 0,
|
|
515
|
-
lastActiveAt: latestActivityMs > 0 ? new Date(latestActivityMs).toISOString() : null,
|
|
516
|
-
extra: { inputTokens, outputTokens, cacheReadTokens },
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
catch {
|
|
520
|
-
return null;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
async readSkillUsage(lastSyncAt) {
|
|
524
|
-
// Walk the same JSONL files as readUsageStats and detect actual skill
|
|
525
|
-
// invocations. Two verified paths exist in the data:
|
|
526
|
-
//
|
|
527
|
-
// Path A (model-initiated): assistant message contains a tool_use block
|
|
528
|
-
// with name === "Skill" and input.skill === "<skill-name>".
|
|
529
|
-
//
|
|
530
|
-
// Path B (user-initiated slash command): user message contains
|
|
531
|
-
// <command-name>/xxx</command-name>, and the NEXT user message starts
|
|
532
|
-
// with "Base directory for this skill:" (confirming the harness treated
|
|
533
|
-
// it as a skill, not a built-in command).
|
|
534
|
-
//
|
|
535
|
-
// MCP skill tools (mcp__*__skill_*) are listed in deferred-tool blocks
|
|
536
|
-
// but are never actually invoked in any observed JSONL data.
|
|
537
|
-
try {
|
|
538
|
-
const projectsDir = join(homedir(), '.claude', 'projects');
|
|
539
|
-
if (!existsSync(projectsDir))
|
|
540
|
-
return null;
|
|
541
|
-
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
542
|
-
const skillCounts = new Map();
|
|
543
|
-
const recordSkill = (name, ts) => {
|
|
544
|
-
const existing = skillCounts.get(name);
|
|
545
|
-
if (existing) {
|
|
546
|
-
existing.count++;
|
|
547
|
-
if (ts > existing.lastTs)
|
|
548
|
-
existing.lastTs = ts;
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
skillCounts.set(name, { count: 1, lastTs: ts });
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
let cwdEntries;
|
|
555
|
-
try {
|
|
556
|
-
cwdEntries = readdirSync(projectsDir);
|
|
557
|
-
}
|
|
558
|
-
catch {
|
|
559
|
-
return null;
|
|
560
|
-
}
|
|
561
|
-
for (const cwd of cwdEntries) {
|
|
562
|
-
const cwdPath = join(projectsDir, cwd);
|
|
563
|
-
let files;
|
|
564
|
-
try {
|
|
565
|
-
files = readdirSync(cwdPath);
|
|
566
|
-
}
|
|
567
|
-
catch {
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
for (const file of files) {
|
|
571
|
-
if (!file.endsWith('.jsonl'))
|
|
572
|
-
continue;
|
|
573
|
-
const filePath = join(cwdPath, file);
|
|
574
|
-
let fileStat;
|
|
575
|
-
try {
|
|
576
|
-
fileStat = statSync(filePath);
|
|
577
|
-
}
|
|
578
|
-
catch {
|
|
579
|
-
continue;
|
|
580
|
-
}
|
|
581
|
-
if (fileStat.mtimeMs <= sinceMs)
|
|
582
|
-
continue;
|
|
583
|
-
let content;
|
|
584
|
-
try {
|
|
585
|
-
content = readFileSync(filePath, 'utf-8');
|
|
586
|
-
}
|
|
587
|
-
catch {
|
|
588
|
-
continue;
|
|
589
|
-
}
|
|
590
|
-
// Track pending slash command for Path B lookahead
|
|
591
|
-
let pendingCommand = null;
|
|
592
|
-
for (const line of content.split('\n')) {
|
|
593
|
-
if (!line)
|
|
594
|
-
continue;
|
|
595
|
-
let entry;
|
|
596
|
-
try {
|
|
597
|
-
entry = JSON.parse(line);
|
|
598
|
-
}
|
|
599
|
-
catch {
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
const tsRaw = entry.timestamp;
|
|
603
|
-
if (typeof tsRaw !== 'string')
|
|
604
|
-
continue;
|
|
605
|
-
const ts = Date.parse(tsRaw);
|
|
606
|
-
if (!Number.isFinite(ts) || ts <= sinceMs)
|
|
607
|
-
continue;
|
|
608
|
-
const type = entry.type;
|
|
609
|
-
// Path A: Skill tool calls in assistant messages
|
|
610
|
-
if (type === 'assistant') {
|
|
611
|
-
const msg = entry.message;
|
|
612
|
-
if (msg && typeof msg === 'object') {
|
|
613
|
-
const blocks = Array.isArray(msg.content)
|
|
614
|
-
? msg.content
|
|
615
|
-
: [];
|
|
616
|
-
for (const block of blocks) {
|
|
617
|
-
if (block && block.type === 'tool_use' && block.name === 'Skill') {
|
|
618
|
-
const input = block.input;
|
|
619
|
-
if (input && typeof input.skill === 'string') {
|
|
620
|
-
recordSkill(input.skill, ts);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
// Path B: slash command -> skill content injection
|
|
627
|
-
if (type === 'user') {
|
|
628
|
-
const msg = entry.message;
|
|
629
|
-
const msgContent = msg?.content;
|
|
630
|
-
const text = typeof msgContent === 'string' ? msgContent : '';
|
|
631
|
-
// Check if this is a skill content injection following a slash command
|
|
632
|
-
if (pendingCommand && text.startsWith('Base directory for this skill:')) {
|
|
633
|
-
recordSkill(pendingCommand.name, pendingCommand.ts);
|
|
634
|
-
pendingCommand = null;
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
637
|
-
// Reset pending on any user message that isn't skill content
|
|
638
|
-
if (pendingCommand && type === 'user') {
|
|
639
|
-
pendingCommand = null;
|
|
640
|
-
}
|
|
641
|
-
// Check for slash command pattern
|
|
642
|
-
const match = text.match(/<command-name>\/?([^<]+)<\/command-name>/);
|
|
643
|
-
if (match) {
|
|
644
|
-
const cmdName = match[1].replace(/^\//, '');
|
|
645
|
-
pendingCommand = { name: cmdName, ts };
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
if (skillCounts.size === 0)
|
|
652
|
-
return null;
|
|
653
|
-
const results = [];
|
|
654
|
-
for (const [skillName, { count, lastTs }] of skillCounts) {
|
|
655
|
-
results.push({
|
|
656
|
-
skillName,
|
|
657
|
-
count,
|
|
658
|
-
lastUsedAt: new Date(lastTs).toISOString(),
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
return results;
|
|
662
|
-
}
|
|
663
|
-
catch {
|
|
664
|
-
return null;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
async readVersion() {
|
|
668
|
-
try {
|
|
669
|
-
const output = execFileSync('claude', ['--version'], { stdio: 'pipe' }).toString().trim();
|
|
670
|
-
return output;
|
|
671
|
-
}
|
|
672
|
-
catch {
|
|
673
|
-
return null;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
// ── Plugin helpers ─────────────────────────────────────────────────
|
|
677
|
-
getPluginsBaseDir() {
|
|
678
|
-
return join(homedir(), '.claude', 'plugins');
|
|
679
|
-
}
|
|
680
|
-
getPluginDir() {
|
|
681
|
-
return join(this.getPluginsBaseDir(), 'cache', 'runwork', PLUGIN_NAME, PLUGIN_VERSION);
|
|
682
|
-
}
|
|
683
|
-
/** The marketplace root (contains marketplace.json and plugins/) */
|
|
684
|
-
getMarketplaceRoot() {
|
|
685
|
-
return join(this.getPluginsBaseDir(), 'marketplaces', 'runwork');
|
|
686
|
-
}
|
|
687
|
-
/** The plugin directory within the marketplace */
|
|
688
|
-
getMarketplaceDir() {
|
|
689
|
-
return join(this.getMarketplaceRoot(), 'plugins', PLUGIN_NAME);
|
|
690
|
-
}
|
|
691
|
-
registerPlugin(pluginDir) {
|
|
692
|
-
const pluginsBase = this.getPluginsBaseDir();
|
|
693
|
-
mkdirSync(pluginsBase, { recursive: true });
|
|
694
|
-
// Register in installed_plugins.json
|
|
695
|
-
const installedPath = join(pluginsBase, 'installed_plugins.json');
|
|
696
|
-
const installed = readJsonConfig(installedPath);
|
|
697
|
-
if (!installed.version)
|
|
698
|
-
installed.version = 2;
|
|
699
|
-
if (!installed.plugins)
|
|
700
|
-
installed.plugins = {};
|
|
701
|
-
const pluginKey = `${PLUGIN_NAME}@runwork`;
|
|
702
|
-
const wasRegistered = !!installed.plugins[pluginKey];
|
|
703
|
-
installed.plugins[pluginKey] = [{
|
|
704
|
-
scope: 'user',
|
|
705
|
-
installPath: pluginDir,
|
|
706
|
-
version: PLUGIN_VERSION,
|
|
707
|
-
installedAt: new Date().toISOString(),
|
|
708
|
-
lastUpdated: new Date().toISOString(),
|
|
709
|
-
}];
|
|
710
|
-
writeJsonConfig(installedPath, installed);
|
|
711
|
-
// Write marketplace.json catalog
|
|
712
|
-
const marketRoot = this.getMarketplaceRoot();
|
|
713
|
-
const marketCatalogDir = join(marketRoot, '.claude-plugin');
|
|
714
|
-
mkdirSync(marketCatalogDir, { recursive: true });
|
|
715
|
-
writeFileSync(join(marketCatalogDir, 'marketplace.json'), JSON.stringify(getMarketplaceJson(), null, 2));
|
|
716
|
-
// Register in known_marketplaces.json
|
|
717
|
-
const marketplacesPath = join(pluginsBase, 'known_marketplaces.json');
|
|
718
|
-
const marketplaces = readJsonConfig(marketplacesPath);
|
|
719
|
-
marketplaces['runwork'] = {
|
|
720
|
-
source: { source: 'directory', path: marketRoot },
|
|
721
|
-
installLocation: marketRoot,
|
|
722
|
-
lastUpdated: new Date().toISOString(),
|
|
723
|
-
};
|
|
724
|
-
writeJsonConfig(marketplacesPath, marketplaces);
|
|
725
|
-
console.log(` [Claude Code] ${wasRegistered ? 'Refreshed' : 'Registered'} plugin ` +
|
|
726
|
-
`${pluginKey} v${PLUGIN_VERSION} -> ${installedPath}`);
|
|
727
|
-
// Enable the plugin in settings.json
|
|
728
|
-
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
729
|
-
const settings = readJsonConfig(settingsPath);
|
|
730
|
-
if (!settings['enabledPlugins']) {
|
|
731
|
-
settings['enabledPlugins'] = {};
|
|
732
|
-
}
|
|
733
|
-
const enabled = settings['enabledPlugins'];
|
|
734
|
-
if (!enabled[`${PLUGIN_NAME}@runwork`]) {
|
|
735
|
-
enabled[`${PLUGIN_NAME}@runwork`] = true;
|
|
736
|
-
writeJsonConfig(settingsPath, settings);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* Remove old skill directory that used the display name (with spaces) if it
|
|
742
|
-
* differs from the new slug-based directory name.
|
|
743
|
-
*/
|
|
744
|
-
function cleanupOldSkillDir(baseDir, skill) {
|
|
745
|
-
if (skill.name === skill.filename)
|
|
746
|
-
return;
|
|
747
|
-
const oldDir = join(baseDir, skill.name);
|
|
748
|
-
if (existsSync(oldDir)) {
|
|
749
|
-
try {
|
|
750
|
-
rmSync(oldDir, { recursive: true, force: true });
|
|
751
|
-
}
|
|
752
|
-
catch { /* best-effort */ }
|
|
753
|
-
}
|
|
754
|
-
}
|