crewly 1.15.0 → 1.17.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/README.md +39 -5
- package/config/roles/developer/prompt.md +1 -1
- package/config/roles/orchestrator/prompt.md +20 -5
- package/config/runtime_scripts/runtime-config.json +7 -0
- package/config/skills/agent/core/calendar-create/SKILL.md +62 -0
- package/config/skills/agent/core/calendar-create/execute.sh +91 -0
- package/config/skills/agent/core/calendar-create/execute.test.sh +80 -0
- package/config/skills/agent/core/calendar-list/SKILL.md +58 -0
- package/config/skills/agent/core/calendar-list/execute.sh +88 -0
- package/config/skills/agent/core/calendar-list/execute.test.sh +71 -0
- package/config/skills/agent/core/count-json/SKILL.md +61 -0
- package/config/skills/agent/core/count-json/execute.sh +52 -0
- package/config/skills/agent/core/count-json/execute.test.sh +35 -0
- package/config/skills/agent/core/gmail-read/SKILL.md +61 -0
- package/config/skills/agent/core/gmail-read/execute.sh +62 -0
- package/config/skills/agent/core/gmail-read/execute.test.sh +68 -0
- package/config/skills/agent/core/gmail-search/SKILL.md +64 -0
- package/config/skills/agent/core/gmail-search/execute.sh +70 -0
- package/config/skills/agent/core/gmail-search/execute.test.sh +67 -0
- package/config/skills/agent/core/gmail-send/SKILL.md +69 -0
- package/config/skills/agent/core/gmail-send/execute.sh +114 -0
- package/config/skills/agent/core/gmail-send/execute.test.sh +83 -0
- package/config/skills/agent/core/record-prediction/SKILL.md +61 -0
- package/config/skills/agent/core/record-prediction/execute.sh +90 -0
- package/config/skills/agent/core/record-prediction/execute.test.sh +67 -0
- package/config/skills/agent/core/resolve-prediction/SKILL.md +61 -0
- package/config/skills/agent/core/resolve-prediction/execute.sh +87 -0
- package/config/skills/agent/core/resolve-prediction/execute.test.sh +74 -0
- package/config/skills/agent/core/set-focus/SKILL.md +64 -0
- package/config/skills/agent/core/set-focus/execute.sh +73 -0
- package/config/skills/agent/core/set-focus/execute.test.sh +67 -0
- package/config/skills/agent/core/suppress-noise/SKILL.md +59 -0
- package/config/skills/agent/core/suppress-noise/execute.sh +68 -0
- package/config/skills/agent/core/suppress-noise/execute.test.sh +58 -0
- package/config/skills/orchestrator/list-missions/SKILL.md +47 -0
- package/config/skills/orchestrator/list-missions/execute.sh +34 -0
- package/config/skills/orchestrator/list-missions/execute.test.sh +33 -0
- package/config/slack-master-app-manifest.json +64 -0
- package/dist/backend/backend/src/constants.d.ts +186 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +196 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/agent-self-improvement/agent-self-improvement.controller.d.ts +76 -0
- package/dist/backend/backend/src/controllers/agent-self-improvement/agent-self-improvement.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/agent-self-improvement/agent-self-improvement.controller.js +262 -0
- package/dist/backend/backend/src/controllers/agent-self-improvement/agent-self-improvement.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +3 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts +100 -0
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.controller.js +293 -0
- package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts +26 -0
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.routes.js +38 -0
- package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/oauth/oauth.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/oauth/oauth.routes.js +31 -0
- package/dist/backend/backend/src/controllers/oauth/oauth.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/request-types.d.ts +6 -2
- package/dist/backend/backend/src/controllers/request-types.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +209 -2
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.js +4 -0
- package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts +1 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +92 -8
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +10 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +10 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +32 -4
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/opencode-runtime.service.d.ts +114 -0
- package/dist/backend/backend/src/services/agent/opencode-runtime.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/opencode-runtime.service.js +185 -0
- package/dist/backend/backend/src/services/agent/opencode-runtime.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +22 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js +15 -3
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-service.factory.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-service.factory.js +9 -0
- package/dist/backend/backend/src/services/agent/runtime-service.factory.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts +23 -0
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js +45 -0
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/memory-reference.module.d.ts +43 -3
- package/dist/backend/backend/src/services/ai/prompt-modules/memory-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/memory-reference.module.js +85 -4
- package/dist/backend/backend/src/services/ai/prompt-modules/memory-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/prompt-module.interface.d.ts +2 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/prompt-module.interface.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/prompt-module.interface.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +6 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/ai/self-improvement/agent-memory-provider.d.ts +45 -0
- package/dist/backend/backend/src/services/ai/self-improvement/agent-memory-provider.d.ts.map +1 -0
- package/dist/backend/backend/src/services/ai/self-improvement/agent-memory-provider.js +59 -0
- package/dist/backend/backend/src/services/ai/self-improvement/agent-memory-provider.js.map +1 -0
- package/dist/backend/backend/src/services/ai/self-improvement/consolidation-scheduler.service.d.ts +137 -0
- package/dist/backend/backend/src/services/ai/self-improvement/consolidation-scheduler.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/ai/self-improvement/consolidation-scheduler.service.js +216 -0
- package/dist/backend/backend/src/services/ai/self-improvement/consolidation-scheduler.service.js.map +1 -0
- package/dist/backend/backend/src/services/ai/self-improvement/prediction-calibration.service.d.ts +4 -1
- package/dist/backend/backend/src/services/ai/self-improvement/prediction-calibration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/self-improvement/prediction-calibration.service.js +3 -1
- package/dist/backend/backend/src/services/ai/self-improvement/prediction-calibration.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts +8 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js +25 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/backend/backend/src/services/google/calendar.service.d.ts +98 -0
- package/dist/backend/backend/src/services/google/calendar.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/calendar.service.js +125 -0
- package/dist/backend/backend/src/services/google/calendar.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/gmail.service.d.ts +165 -0
- package/dist/backend/backend/src/services/google/gmail.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/gmail.service.js +296 -0
- package/dist/backend/backend/src/services/google/gmail.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts +44 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/google-api.client.js +104 -0
- package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +159 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js +274 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -0
- package/dist/backend/backend/src/services/mcp-tool-definitions.d.ts +1 -1
- package/dist/backend/backend/src/services/mcp-tool-definitions.js +1 -1
- package/dist/backend/backend/src/services/mcp-tool-definitions.js.map +1 -1
- package/dist/backend/backend/src/services/runtime-adapter.d.ts +10 -1
- package/dist/backend/backend/src/services/runtime-adapter.d.ts.map +1 -1
- package/dist/backend/backend/src/services/runtime-adapter.js +12 -1
- package/dist/backend/backend/src/services/runtime-adapter.js.map +1 -1
- package/dist/backend/backend/src/services/settings/settings.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/settings/settings.service.js +1 -0
- package/dist/backend/backend/src/services/settings/settings.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js +30 -0
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts +186 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js +409 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts +95 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +284 -4
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts +178 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js +392 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +76 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +234 -47
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/fallback-trigger-cleanup.subscriber.d.ts +78 -0
- package/dist/backend/backend/src/services/v3/fallback-trigger-cleanup.subscriber.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/fallback-trigger-cleanup.subscriber.js +129 -0
- package/dist/backend/backend/src/services/v3/fallback-trigger-cleanup.subscriber.js.map +1 -0
- package/dist/backend/backend/src/services/v3/kr-skill-measurer.service.d.ts +117 -0
- package/dist/backend/backend/src/services/v3/kr-skill-measurer.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/kr-skill-measurer.service.js +214 -0
- package/dist/backend/backend/src/services/v3/kr-skill-measurer.service.js.map +1 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts +6 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js +33 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts +116 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js +254 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js.map +1 -0
- package/dist/backend/backend/src/types/index.d.ts +11 -1
- package/dist/backend/backend/src/types/index.d.ts.map +1 -1
- package/dist/backend/backend/src/types/index.js.map +1 -1
- package/dist/backend/backend/src/types/settings.types.d.ts +1 -1
- package/dist/backend/backend/src/types/settings.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/settings.types.js +7 -0
- package/dist/backend/backend/src/types/settings.types.js.map +1 -1
- package/dist/backend/backend/src/types/slack.types.d.ts +141 -2
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/backend/src/types/team-template.types.d.ts +2 -2
- package/dist/backend/backend/src/types/team-template.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/team-template.types.js +1 -1
- package/dist/backend/backend/src/types/team-template.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.d.ts +2 -0
- package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.js.map +1 -1
- package/dist/backend/backend/src/utils/terminal-string-ops.d.ts +2 -0
- package/dist/backend/backend/src/utils/terminal-string-ops.d.ts.map +1 -1
- package/dist/backend/backend/src/utils/terminal-string-ops.js +14 -0
- package/dist/backend/backend/src/utils/terminal-string-ops.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +186 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +196 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +10 -0
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +32 -4
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/opencode-runtime.service.d.ts +114 -0
- package/dist/cli/backend/src/services/agent/opencode-runtime.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/opencode-runtime.service.js +185 -0
- package/dist/cli/backend/src/services/agent/opencode-runtime.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +22 -1
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js +15 -3
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-service.factory.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-service.factory.js +9 -0
- package/dist/cli/backend/src/services/agent/runtime-service.factory.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-builder.service.d.ts +23 -0
- package/dist/cli/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-builder.service.js +45 -0
- package/dist/cli/backend/src/services/ai/prompt-builder.service.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.d.ts +43 -3
- package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.js +85 -4
- package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.d.ts +2 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +6 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/ai/self-improvement/agent-memory-provider.d.ts +45 -0
- package/dist/cli/backend/src/services/ai/self-improvement/agent-memory-provider.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/self-improvement/agent-memory-provider.js +59 -0
- package/dist/cli/backend/src/services/ai/self-improvement/agent-memory-provider.js.map +1 -0
- package/dist/cli/backend/src/services/ai/self-improvement/attention.service.d.ts +74 -0
- package/dist/cli/backend/src/services/ai/self-improvement/attention.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/self-improvement/attention.service.js +132 -0
- package/dist/cli/backend/src/services/ai/self-improvement/attention.service.js.map +1 -0
- package/dist/cli/backend/src/services/ai/self-improvement/memory-consolidation.service.d.ts +107 -0
- package/dist/cli/backend/src/services/ai/self-improvement/memory-consolidation.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/self-improvement/memory-consolidation.service.js +183 -0
- package/dist/cli/backend/src/services/ai/self-improvement/memory-consolidation.service.js.map +1 -0
- package/dist/cli/backend/src/services/ai/self-improvement/prediction-calibration.service.d.ts +100 -0
- package/dist/cli/backend/src/services/ai/self-improvement/prediction-calibration.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/self-improvement/prediction-calibration.service.js +130 -0
- package/dist/cli/backend/src/services/ai/self-improvement/prediction-calibration.service.js.map +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/cli/backend/src/services/mcp-tool-definitions.d.ts +1 -1
- package/dist/cli/backend/src/services/mcp-tool-definitions.js +1 -1
- package/dist/cli/backend/src/services/mcp-tool-definitions.js.map +1 -1
- package/dist/cli/backend/src/services/settings/settings.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/settings/settings.service.js +1 -0
- package/dist/cli/backend/src/services/settings/settings.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js +30 -0
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +76 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +234 -47
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/types/index.d.ts +11 -1
- package/dist/cli/backend/src/types/index.d.ts.map +1 -1
- package/dist/cli/backend/src/types/index.js.map +1 -1
- package/dist/cli/backend/src/types/settings.types.d.ts +1 -1
- package/dist/cli/backend/src/types/settings.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/settings.types.js +7 -0
- package/dist/cli/backend/src/types/settings.types.js.map +1 -1
- package/dist/cli/backend/src/types/slack.types.d.ts +141 -2
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/dist/cli/backend/src/types/team-template.types.d.ts +2 -2
- package/dist/cli/backend/src/types/team-template.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/team-template.types.js +1 -1
- package/dist/cli/backend/src/types/team-template.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/mission.types.d.ts +2 -0
- package/dist/cli/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/mission.types.js.map +1 -1
- package/dist/cli/backend/src/utils/terminal-string-ops.d.ts +2 -0
- package/dist/cli/backend/src/utils/terminal-string-ops.d.ts.map +1 -1
- package/dist/cli/backend/src/utils/terminal-string-ops.js +14 -0
- package/dist/cli/backend/src/utils/terminal-string-ops.js.map +1 -1
- package/dist/cli/cli/src/commands/onboard.d.ts +2 -2
- package/dist/cli/cli/src/commands/onboard.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/onboard.js +15 -7
- package/dist/cli/cli/src/commands/onboard.js.map +1 -1
- package/frontend/dist/assets/{index-203224f9.js → index-c87332bf.js} +278 -278
- package/frontend/dist/assets/index-f6b4230e.css +42 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-dcaf6c88.css +0 -42
package/README.md
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
**Website:** [crewlyai.com](https://crewlyai.com/)
|
|
9
9
|
|
|
10
|
-
Crewly is an open-source multi-agent orchestration platform that coordinates AI coding agents (Claude Code, Gemini CLI, Codex) to work together as a team. It provides a web dashboard for real-time monitoring, task management, and team coordination — all running locally on your machine.
|
|
10
|
+
Crewly is an open-source multi-agent orchestration platform that coordinates AI coding agents (Claude Code, Gemini CLI, Codex, OpenCode) to work together as a team. It provides a web dashboard for real-time monitoring, task management, and team coordination — all running locally on your machine.
|
|
11
11
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
- **Multi-agent teams** — Create teams with different roles (developer, QA, PM, orchestrator) and watch them collaborate
|
|
15
|
-
- **Multi-runtime support** — Use Claude Code, Gemini CLI,
|
|
15
|
+
- **Multi-runtime support** — Use Claude Code, Gemini CLI, OpenAI Codex, or OpenCode — mix and match per agent
|
|
16
16
|
- **Real-time dashboard** — Monitor all agents through live terminal streams, task boards, and activity feeds
|
|
17
17
|
- **Skill system** — Agents coordinate through bash skills (report status, delegate tasks, manage memory)
|
|
18
18
|
- **Agent memory** — Persistent knowledge that agents build and share across sessions
|
|
@@ -49,8 +49,9 @@ The `init` command walks you through provider selection, installs agent skills,
|
|
|
49
49
|
| **Claude Code** (default) | `npm install -g @anthropic-ai/claude-code` | `claude --version` |
|
|
50
50
|
| **Gemini CLI** | `npm install -g @google/gemini-cli` | `gemini --version` |
|
|
51
51
|
| **Codex (OpenAI)** | `npm install -g @openai/codex` | `codex --version` |
|
|
52
|
+
| **OpenCode** | `npm install -g opencode-ai` | `opencode --version` |
|
|
52
53
|
|
|
53
|
-
**API keys:** Gemini CLI requires `GEMINI_API_KEY`. Codex requires an OpenAI API key. Claude Code authenticates through its own login flow.
|
|
54
|
+
**API keys:** Gemini CLI requires `GEMINI_API_KEY`. Codex requires an OpenAI API key. Claude Code authenticates through its own login flow. OpenCode uses whichever provider you connect via `opencode auth login` (or the `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GEMINI_API_KEY` env vars Crewly already exports to agent sessions).
|
|
54
55
|
|
|
55
56
|
## Architecture
|
|
56
57
|
|
|
@@ -98,7 +99,8 @@ Storage: ~/.crewly/ (global) + project/.crewly/ (per-project)
|
|
|
98
99
|
|---------|-----------------|-------|
|
|
99
100
|
| **Claude Code** | `claude --dangerously-skip-permissions` | Default runtime |
|
|
100
101
|
| **Gemini CLI** | `gemini --yolo` | Requires `GEMINI_API_KEY` |
|
|
101
|
-
| **Codex (OpenAI)** | `codex
|
|
102
|
+
| **Codex (OpenAI)** | `codex -a never -s danger-full-access` | Requires OpenAI API key |
|
|
103
|
+
| **OpenCode** | `opencode --auto` | Any provider/model (`-m provider/model`); reads `AGENTS.md`; auth via `opencode auth login` |
|
|
102
104
|
|
|
103
105
|
You can change the default runtime or customize launch commands in **Settings**.
|
|
104
106
|
|
|
@@ -123,9 +125,11 @@ Optional environment variables (`.env` file or shell):
|
|
|
123
125
|
```bash
|
|
124
126
|
GEMINI_API_KEY=your_key_here # Required for Gemini CLI runtime
|
|
125
127
|
|
|
126
|
-
SLACK_BOT_TOKEN=xoxb-... # Optional: Slack
|
|
128
|
+
SLACK_BOT_TOKEN=xoxb-... # Optional: self-hosted Slack app (see "Slack" below)
|
|
127
129
|
SLACK_APP_TOKEN=xapp-...
|
|
128
130
|
SLACK_SIGNING_SECRET=...
|
|
131
|
+
CREWLY_SLACK_SOURCE=cloud # Optional: env | cloud (unset = Cloud wins when both exist)
|
|
132
|
+
CREWLY_SLACK_PRIMARY=1 # Optional: this instance handles DMs / unmapped channels
|
|
129
133
|
|
|
130
134
|
LOG_LEVEL=info # debug, info, warn, error
|
|
131
135
|
WEB_PORT=8787 # Dashboard port (default: 8787)
|
|
@@ -133,6 +137,36 @@ CREWLY_BIND_HOST=0.0.0.0 # Interface to listen on (127.0.0.1 = this ma
|
|
|
133
137
|
CREWLY_API_TOKEN=... # Pin the API token (otherwise generated at ~/.crewly/api-token)
|
|
134
138
|
```
|
|
135
139
|
|
|
140
|
+
### Slack
|
|
141
|
+
|
|
142
|
+
**One click (recommended).** Log in to Crewly Cloud (Settings → Cloud), open
|
|
143
|
+
Settings → Integrations → Slack and press **Connect Slack**. Slack asks you to
|
|
144
|
+
approve the Crewly app once; from then on every Crewly instance signed in to the
|
|
145
|
+
same Crewly account gets Slack automatically — team channels, and one real Slack
|
|
146
|
+
bot user per agent — with no tokens to copy. The instance pulls its config from
|
|
147
|
+
Cloud on boot and every 10 minutes (cached 0600 at
|
|
148
|
+
`~/.crewly/slack-cloud-config.json`); Slack events reach it through the Cloud
|
|
149
|
+
relay, so no Socket Mode connection is opened.
|
|
150
|
+
|
|
151
|
+
**The one manual step.** Slack only lets an app be created by an *App
|
|
152
|
+
Configuration Token* of a workspace member, and Crewly creates one Slack app per
|
|
153
|
+
agent so each agent is a real bot user (name in the member list, native `@`
|
|
154
|
+
mentions). Generate the token at api.slack.com/apps ("Your App Configuration
|
|
155
|
+
Tokens" → Generate) and paste it in Settings → Slack → Agent Identities. Cloud
|
|
156
|
+
keeps it refreshed. Each new agent then shows an install link you click once.
|
|
157
|
+
|
|
158
|
+
**Several instances, one workspace.** Every instance registers its teams,
|
|
159
|
+
channels and agents with Cloud (on boot, on team changes, every 5 min); Slack
|
|
160
|
+
traffic for a team channel goes to the instance running that team. Direct
|
|
161
|
+
messages to the Crewly bot and channels no team owns go to the **primary**
|
|
162
|
+
instance — the Settings toggle, or `CREWLY_SLACK_PRIMARY=1`.
|
|
163
|
+
|
|
164
|
+
**Self-hosted app.** The original path (your own Slack app, Socket Mode,
|
|
165
|
+
`SLACK_BOT_TOKEN` / `SLACK_APP_TOKEN` / `SLACK_SIGNING_SECRET` or the form under
|
|
166
|
+
Settings → Slack → Advanced) still works. `CREWLY_SLACK_SOURCE` picks the source:
|
|
167
|
+
`cloud` uses only the Cloud workspace, `env` uses only local tokens and never
|
|
168
|
+
asks Cloud, unset prefers Cloud when both exist (logged once at boot).
|
|
169
|
+
|
|
136
170
|
### Securing a server install
|
|
137
171
|
|
|
138
172
|
Crewly agents run as real shells on the host, and `POST /api/terminal/:session/write`
|
|
@@ -273,7 +273,7 @@ When you are **idle and have no assigned tasks**, proactively check the Task Poo
|
|
|
273
273
|
|
|
274
274
|
Call the `poll-tasks` skill to query and claim work from the shared Task Pool:
|
|
275
275
|
```bash
|
|
276
|
-
bash {{AGENT_SKILLS_PATH}}/core/poll-tasks/execute.sh '{"sessionName":"{{SESSION_NAME}}","role":"{{ROLE}}","skills":
|
|
276
|
+
bash {{AGENT_SKILLS_PATH}}/core/poll-tasks/execute.sh '{"sessionName":"{{SESSION_NAME}}","role":"{{ROLE}}","skills":{{MEMBER_SKILLS_JSON}},"projectPath":"{{PROJECT_PATH}}"}'
|
|
277
277
|
```
|
|
278
278
|
|
|
279
279
|
### Parameters
|
|
@@ -194,17 +194,30 @@ bash {{ORCHESTRATOR_SKILLS_PATH}}/register-self/execute.sh '{"role":"orchestrato
|
|
|
194
194
|
|
|
195
195
|
After registering, proceed to Step 4.
|
|
196
196
|
|
|
197
|
-
### Step 4 — Check
|
|
197
|
+
### Step 4 — Check the Missions (OKRs) and Report
|
|
198
198
|
|
|
199
|
-
After registration,
|
|
199
|
+
After registration, read the real goal layer — the Missions API, not your
|
|
200
|
+
memory (memory only holds what someone happened to save):
|
|
200
201
|
|
|
201
202
|
```bash
|
|
202
|
-
bash {{
|
|
203
|
+
bash {{ORCHESTRATOR_SKILLS_PATH}}/list-missions/execute.sh
|
|
203
204
|
```
|
|
204
205
|
|
|
205
|
-
**If
|
|
206
|
+
**If a proposal is `pending_approval`:** tell the owner in one line what is
|
|
207
|
+
waiting (objective + its Key Results) and where to approve it (Missions page).
|
|
208
|
+
Do NOT execute, remind about or decompose it until it is approved.
|
|
206
209
|
|
|
207
|
-
**If
|
|
210
|
+
**If live (approved) missions exist:** report each one's KRs (current → target,
|
|
211
|
+
status) in two or three lines and ask whether to take over execution. Do NOT
|
|
212
|
+
auto-execute unless the owner activates Autonomous Mode (below). Once
|
|
213
|
+
activated in a session it stays ON for that session.
|
|
214
|
+
|
|
215
|
+
**If there are no missions at all** and `get-team-status` shows teams that
|
|
216
|
+
completed work in the last week: draft ONE company-level OKR from that work
|
|
217
|
+
(objective + 2–3 Key Results that can be measured from something already in
|
|
218
|
+
the system — a ticket board, a build log, a folder count; see `count-json`
|
|
219
|
+
and `measurementSource: skill_output`) and ask the owner whether to create it.
|
|
220
|
+
Do not create it yourself. If there is no recent work either, say "Ready".
|
|
208
221
|
|
|
209
222
|
## Pipeline-First Planning Discipline (MANDATORY for planning intent)
|
|
210
223
|
|
|
@@ -271,6 +284,8 @@ The `delegate-task` skill emits a stderr WARNING when a brief is missing G/O/E m
|
|
|
271
284
|
|
|
272
285
|
5. **Self-check before dispatch** — diff your composed `Expected Outcome` line items against the user's literal words. Every line in Expected Outcome must trace to a word/phrase the user actually wrote. If a line is justified only by recalled history, move it to `Context` or `Suggested Follow-ups` and surface back for confirmation.
|
|
273
286
|
|
|
287
|
+
6. **Outbound mail under the owner's name is gated.** Sending email from the owner's Gmail (`gmail-send`, or any delegated brief that includes sending) requires the owner's explicit go-ahead in this conversation unless the task brief already grants it in `Decision Rights`; without that grant, draft with the dry-run preview and show it for approval instead of sending.
|
|
288
|
+
|
|
274
289
|
**Negative pattern to suppress** (the 2026-05-20 ESTestNode shape):
|
|
275
290
|
> User: "update X" → ORC: `recall_memory(X)` → memory has "X had 3 known issues" → ORC: "while you're updating X, handle the 3 known issues too" → executor does all 4 things → user pays for unauthorized scope on prod.
|
|
276
291
|
|
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
"timeout": 90000,
|
|
22
22
|
"description": "OpenAI's Codex CLI for advanced code understanding"
|
|
23
23
|
},
|
|
24
|
+
"opencode-cli": {
|
|
25
|
+
"displayName": "OpenCode",
|
|
26
|
+
"initScript": "",
|
|
27
|
+
"welcomeMessage": "Ask anything",
|
|
28
|
+
"timeout": 90000,
|
|
29
|
+
"description": "OpenCode open-source TUI coding agent (opencode.ai), any provider/model"
|
|
30
|
+
},
|
|
24
31
|
"crewly-agent": {
|
|
25
32
|
"displayName": "Crewly Agent",
|
|
26
33
|
"initScript": "",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Calendar Create
|
|
3
|
+
description: Create an event on the owner's Google Calendar (via the Google Workspace grant held by Crewly Cloud) — timed or all-day, with optional description and attendees. Attendees receive an invitation from the owner, so confirm before inviting others.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: communication
|
|
6
|
+
skillType: claude-skill
|
|
7
|
+
assignableRoles:
|
|
8
|
+
- orchestrator
|
|
9
|
+
- team-leader
|
|
10
|
+
- operations
|
|
11
|
+
- ops
|
|
12
|
+
- sales
|
|
13
|
+
- support
|
|
14
|
+
- generalist
|
|
15
|
+
triggers:
|
|
16
|
+
- create event
|
|
17
|
+
- schedule meeting
|
|
18
|
+
- add to calendar
|
|
19
|
+
- book a meeting
|
|
20
|
+
tags:
|
|
21
|
+
- google
|
|
22
|
+
- calendar
|
|
23
|
+
execution:
|
|
24
|
+
type: script
|
|
25
|
+
script:
|
|
26
|
+
file: execute.sh
|
|
27
|
+
interpreter: bash
|
|
28
|
+
timeoutMs: 30000
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
# Calendar Create
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bash execute.sh --summary "Design review" --start 2026-09-20T14:00:00 --end 2026-09-20T15:00:00 \
|
|
35
|
+
--timezone Asia/Shanghai --description "Walk through v2" --attendee ann@example.com
|
|
36
|
+
bash execute.sh --summary "Offsite" --start 2026-10-01 --end 2026-10-02 # all-day (end is exclusive)
|
|
37
|
+
bash execute.sh '{"summary":"Standup","start":"2026-09-21T09:00:00Z","end":"2026-09-21T09:15:00Z"}'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Parameters
|
|
41
|
+
|
|
42
|
+
| Name | Required | Meaning |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `--summary` (`summary`) | yes | Event title |
|
|
45
|
+
| `--start` (`start`) | yes | ISO 8601 date-time, or `YYYY-MM-DD` for all-day |
|
|
46
|
+
| `--end` (`end`) | yes | ISO 8601 date-time, or `YYYY-MM-DD` (exclusive) for all-day |
|
|
47
|
+
| `--timezone` (`timezone`) | no | IANA zone applied to start/end without an offset |
|
|
48
|
+
| `--description` (`description`) | no | Body text |
|
|
49
|
+
| `--attendee` (`attendees`) | no | Email; repeat for several (JSON: array or comma list) |
|
|
50
|
+
| `--calendar` (`calendarId`) | no | Calendar id; default `primary` |
|
|
51
|
+
|
|
52
|
+
## Output
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{"success":true,"id":"…","summary":"Design review","start":{"dateTime":"…","timeZone":"Asia/Shanghai"},"end":{…},"htmlLink":"https://…","attendees":[{"email":"ann@example.com","responseStatus":"needsAction"}]}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Failures
|
|
59
|
+
|
|
60
|
+
Missing `--summary` / `--start` / `--end` → `{"error":"Missing required parameter: …"}` on stderr, exit 1.
|
|
61
|
+
`{"success":false,"reason":"not_connected","hint":"<connect URL>"}` (exit 1) when the owner has not connected Google Workspace;
|
|
62
|
+
`reason` `validation` when Google cannot parse a time.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# calendar-create — Create an event on the owner's Google Calendar
|
|
4
|
+
#
|
|
5
|
+
# Backed by POST /api/google/calendar/events.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# bash execute.sh --summary "Review" --start 2026-09-20T14:00:00 --end 2026-09-20T15:00:00
|
|
9
|
+
# [--timezone Asia/Shanghai] [--description "…"] [--attendee a@b.c]... [--calendar id]
|
|
10
|
+
# bash execute.sh '{"summary":"Review","start":"…","end":"…","attendees":["a@b.c"]}'
|
|
11
|
+
# =============================================================================
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
source "${SCRIPT_DIR}/../../_common/lib.sh"
|
|
15
|
+
|
|
16
|
+
print_usage() {
|
|
17
|
+
cat <<'EOF_USAGE'
|
|
18
|
+
Usage:
|
|
19
|
+
bash execute.sh --summary "Review" --start <ISO|YYYY-MM-DD> --end <ISO|YYYY-MM-DD>
|
|
20
|
+
[--timezone Asia/Shanghai] [--description "…"] [--attendee a@b.c]... [--calendar <id>]
|
|
21
|
+
bash execute.sh '{"summary":"Review","start":"…","end":"…","attendees":["a@b.c"]}'
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--summary Event title (required)
|
|
25
|
+
--start Start: ISO 8601 date-time, or YYYY-MM-DD for all-day (required)
|
|
26
|
+
--end End: ISO 8601 date-time, or YYYY-MM-DD (exclusive) for all-day (required)
|
|
27
|
+
--timezone IANA zone for start/end without an offset
|
|
28
|
+
--description Body text
|
|
29
|
+
--attendee Attendee email (repeat for several)
|
|
30
|
+
--calendar Calendar id (default: primary)
|
|
31
|
+
--help | -h Show this help
|
|
32
|
+
EOF_USAGE
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
INPUT_JSON=""
|
|
36
|
+
SUMMARY=""; START=""; END=""; TIMEZONE=""; DESCRIPTION=""; CALENDAR=""
|
|
37
|
+
ATTENDEES_JSON='[]'
|
|
38
|
+
|
|
39
|
+
if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
40
|
+
INPUT_JSON="$1"
|
|
41
|
+
shift || true
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
while [[ $# -gt 0 ]]; do
|
|
45
|
+
case "$1" in
|
|
46
|
+
--summary|--title) [ $# -ge 2 ] || error_exit "--summary requires a value"; SUMMARY="$2"; shift 2 ;;
|
|
47
|
+
--start) [ $# -ge 2 ] || error_exit "--start requires a value"; START="$2"; shift 2 ;;
|
|
48
|
+
--end) [ $# -ge 2 ] || error_exit "--end requires a value"; END="$2"; shift 2 ;;
|
|
49
|
+
--timezone|--tz) [ $# -ge 2 ] || error_exit "--timezone requires a value"; TIMEZONE="$2"; shift 2 ;;
|
|
50
|
+
--description) [ $# -ge 2 ] || error_exit "--description requires a value"; DESCRIPTION="$2"; shift 2 ;;
|
|
51
|
+
--calendar|-c) [ $# -ge 2 ] || error_exit "--calendar requires a value"; CALENDAR="$2"; shift 2 ;;
|
|
52
|
+
--attendee) [ $# -ge 2 ] || error_exit "--attendee requires a value"
|
|
53
|
+
ATTENDEES_JSON=$(jq -cn --argjson arr "$ATTENDEES_JSON" --arg v "$2" '$arr + [$v]'); shift 2 ;;
|
|
54
|
+
--help|-h) print_usage; exit 0 ;;
|
|
55
|
+
*) error_exit "Unknown option: $1" ;;
|
|
56
|
+
esac
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
if [ -n "$INPUT_JSON" ]; then
|
|
60
|
+
INPUT=$(read_json_input "$INPUT_JSON")
|
|
61
|
+
[ -z "$SUMMARY" ] && SUMMARY=$(printf '%s' "$INPUT" | jq -r '.summary // .title // empty')
|
|
62
|
+
[ -z "$START" ] && START=$(printf '%s' "$INPUT" | jq -r '.start // empty')
|
|
63
|
+
[ -z "$END" ] && END=$(printf '%s' "$INPUT" | jq -r '.end // empty')
|
|
64
|
+
[ -z "$TIMEZONE" ] && TIMEZONE=$(printf '%s' "$INPUT" | jq -r '.timezone // .timeZone // empty')
|
|
65
|
+
[ -z "$DESCRIPTION" ] && DESCRIPTION=$(printf '%s' "$INPUT" | jq -r '.description // empty')
|
|
66
|
+
[ -z "$CALENDAR" ] && CALENDAR=$(printf '%s' "$INPUT" | jq -r '.calendarId // .calendar // empty')
|
|
67
|
+
if [ "$(printf '%s' "$ATTENDEES_JSON" | jq 'length')" -eq 0 ]; then
|
|
68
|
+
ATTENDEES_JSON=$(printf '%s' "$INPUT" | jq -c '(.attendees // []) | if type == "string" then split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(. != "")) else . end')
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
require_param "summary (--summary)" "$SUMMARY"
|
|
73
|
+
require_param "start (--start)" "$START"
|
|
74
|
+
require_param "end (--end)" "$END"
|
|
75
|
+
|
|
76
|
+
BODY=$(jq -cn --arg summary "$SUMMARY" --arg start "$START" --arg end "$END" \
|
|
77
|
+
--arg timezone "$TIMEZONE" --arg description "$DESCRIPTION" --arg calendarId "$CALENDAR" \
|
|
78
|
+
--argjson attendees "$ATTENDEES_JSON" \
|
|
79
|
+
'{summary: $summary, start: $start, end: $end}
|
|
80
|
+
+ (if $timezone != "" then {timezone: $timezone} else {} end)
|
|
81
|
+
+ (if $description != "" then {description: $description} else {} end)
|
|
82
|
+
+ (if $calendarId != "" then {calendarId: $calendarId} else {} end)
|
|
83
|
+
+ (if ($attendees | length) > 0 then {attendees: $attendees} else {} end)')
|
|
84
|
+
|
|
85
|
+
RESPONSE=$(api_call POST "/google/calendar/events" "$BODY" 2>&1) || {
|
|
86
|
+
printf '%s' "$RESPONSE" | jq -c '{success: false, reason: (.details.error // .details // .error // "unknown"), hint: (.details.hint // ""), message: (.details.message // "")}' 2>/dev/null \
|
|
87
|
+
|| jq -n --arg r "$RESPONSE" '{success: false, reason: $r}'
|
|
88
|
+
exit 1
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
printf '%s' "$RESPONSE" | jq -c '{success: (.success // false)} + (.data | {id, summary, start, end, htmlLink, attendees})'
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for calendar-create — run with: bash execute.test.sh
|
|
3
|
+
# Spins up a python HTTP stub as the backend, asserts the request body the
|
|
4
|
+
# skill sends and the JSON it prints. Exit 0 on pass.
|
|
5
|
+
set -u
|
|
6
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
EXEC="$HERE/execute.sh"
|
|
8
|
+
PASS=0; FAIL=0
|
|
9
|
+
check() {
|
|
10
|
+
local name="$1"; local got="$2"; local want="$3"
|
|
11
|
+
if [ "$got" = "$want" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $name"; echo " want: $want"; echo " got: $got"; fi
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
PORT=18805
|
|
15
|
+
STUB_LOG="$(mktemp)"; export STUB_LOG; export STUB_PORT="$PORT"
|
|
16
|
+
python3 -u <<'PY' >/dev/null 2>&1 &
|
|
17
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
18
|
+
import json, os
|
|
19
|
+
LOG = os.environ['STUB_LOG']; PORT = int(os.environ['STUB_PORT'])
|
|
20
|
+
class H(BaseHTTPRequestHandler):
|
|
21
|
+
def do_POST(self):
|
|
22
|
+
n = int(self.headers.get('content-length', '0')); body = self.rfile.read(n).decode() if n else ''
|
|
23
|
+
data = json.loads(body or '{}')
|
|
24
|
+
open(LOG, 'w').write(json.dumps({'method': 'POST', 'path': self.path, 'body': data}))
|
|
25
|
+
if data.get('summary') == 'no-grant':
|
|
26
|
+
self.send_response(409); self.send_header('Content-Type', 'application/json'); self.end_headers()
|
|
27
|
+
self.wfile.write(json.dumps({'success': False, 'error': 'not_connected', 'message': 'no grant', 'hint': 'https://cloud/start?token=j'}).encode()); return
|
|
28
|
+
if data.get('start') == 'tomorrow':
|
|
29
|
+
self.send_response(400); self.send_header('Content-Type', 'application/json'); self.end_headers()
|
|
30
|
+
self.wfile.write(json.dumps({'success': False, 'error': 'validation', 'message': '"start" must be YYYY-MM-DD or an ISO 8601 date-time', 'hint': 'Fix the request and retry.'}).encode()); return
|
|
31
|
+
tz = data.get('timezone')
|
|
32
|
+
def t(v):
|
|
33
|
+
if len(v) == 10: return {'date': v}
|
|
34
|
+
return {'dateTime': v, 'timeZone': tz} if tz else {'dateTime': v}
|
|
35
|
+
ev = {'id': 'new1', 'summary': data['summary'], 'start': t(data['start']), 'end': t(data['end']), 'htmlLink': 'https://cal/new1',
|
|
36
|
+
'attendees': [{'email': a, 'responseStatus': 'needsAction'} for a in data.get('attendees', [])], 'status': 'confirmed'}
|
|
37
|
+
self.send_response(200); self.send_header('Content-Type', 'application/json'); self.end_headers()
|
|
38
|
+
self.wfile.write(json.dumps({'success': True, 'data': ev}).encode())
|
|
39
|
+
def log_message(self, *a, **k): pass
|
|
40
|
+
HTTPServer(('127.0.0.1', PORT), H).serve_forever()
|
|
41
|
+
PY
|
|
42
|
+
STUB_PID=$!
|
|
43
|
+
disown "$STUB_PID" 2>/dev/null || true
|
|
44
|
+
trap 'kill "$STUB_PID" >/dev/null 2>&1; rm -f "$STUB_LOG"' EXIT
|
|
45
|
+
for i in $(seq 1 30); do curl -s -o /dev/null -m 0.2 "http://127.0.0.1:${PORT}/" 2>/dev/null && break; sleep 0.1; done
|
|
46
|
+
|
|
47
|
+
run() { CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" "$@" 2>/dev/null; }
|
|
48
|
+
run_err() { CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" "$@" 2>&1 >/dev/null | jq -r '.error' 2>/dev/null; }
|
|
49
|
+
|
|
50
|
+
# --- timed event with everything ---
|
|
51
|
+
OUT=$(run --summary "Design review" --start 2026-09-20T14:00:00 --end 2026-09-20T15:00:00 --timezone Asia/Shanghai \
|
|
52
|
+
--description "Walk through v2" --attendee ann@example.com --attendee bob@example.com --calendar ops@example.com)
|
|
53
|
+
check "flags: body" "$(jq -c .body "$STUB_LOG")" '{"summary":"Design review","start":"2026-09-20T14:00:00","end":"2026-09-20T15:00:00","timezone":"Asia/Shanghai","description":"Walk through v2","calendarId":"ops@example.com","attendees":["ann@example.com","bob@example.com"]}'
|
|
54
|
+
check "flags: path" "$(jq -r .path "$STUB_LOG")" "/api/google/calendar/events"
|
|
55
|
+
check "flags: output" "$OUT" '{"success":true,"id":"new1","summary":"Design review","start":{"dateTime":"2026-09-20T14:00:00","timeZone":"Asia/Shanghai"},"end":{"dateTime":"2026-09-20T15:00:00","timeZone":"Asia/Shanghai"},"htmlLink":"https://cal/new1","attendees":[{"email":"ann@example.com","responseStatus":"needsAction"},{"email":"bob@example.com","responseStatus":"needsAction"}]}'
|
|
56
|
+
|
|
57
|
+
# --- all-day via JSON, comma-separated attendees ---
|
|
58
|
+
OUT=$(run '{"summary":"Offsite","start":"2026-10-01","end":"2026-10-02","attendees":"a@b.c, d@e.f"}')
|
|
59
|
+
check "json: body omits optional keys, splits attendees" "$(jq -c .body "$STUB_LOG")" '{"summary":"Offsite","start":"2026-10-01","end":"2026-10-02","attendees":["a@b.c","d@e.f"]}'
|
|
60
|
+
check "json: all-day output" "$(printf '%s' "$OUT" | jq -c '{start, end}')" '{"start":{"date":"2026-10-01"},"end":{"date":"2026-10-02"}}'
|
|
61
|
+
|
|
62
|
+
# --- backend validation surfaces ---
|
|
63
|
+
OUT=$(CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" --summary x --start tomorrow --end 2026-10-02 2>/dev/null); RC=$?
|
|
64
|
+
check "400: exit code" "$RC" "1"
|
|
65
|
+
check "400: reason" "$(printf '%s' "$OUT" | jq -r .reason)" "validation"
|
|
66
|
+
|
|
67
|
+
# --- 409 ---
|
|
68
|
+
OUT=$(CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" --summary no-grant --start 2026-10-01 --end 2026-10-02 2>/dev/null)
|
|
69
|
+
check "409: reason + hint" "$OUT" '{"success":false,"reason":"not_connected","hint":"https://cloud/start?token=j","message":"no grant"}'
|
|
70
|
+
|
|
71
|
+
# --- local validation (nothing sent) ---
|
|
72
|
+
: > "$STUB_LOG"
|
|
73
|
+
check "missing summary errors" "$(run_err --start 2026-10-01 --end 2026-10-02)" "Missing required parameter: summary (--summary)"
|
|
74
|
+
check "missing start errors" "$(run_err --summary x --end 2026-10-02)" "Missing required parameter: start (--start)"
|
|
75
|
+
check "missing end errors" "$(run_err --summary x --start 2026-10-01)" "Missing required parameter: end (--end)"
|
|
76
|
+
check "validation: no request made" "$(cat "$STUB_LOG")" ""
|
|
77
|
+
check "unknown option errors" "$(run_err --bogus 1)" "Unknown option: --bogus"
|
|
78
|
+
|
|
79
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
80
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Calendar List
|
|
3
|
+
description: List upcoming events on the owner's Google Calendar (via the Google Workspace grant held by Crewly Cloud). Defaults to the next 7 days on the primary calendar. Read-only.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: communication
|
|
6
|
+
skillType: claude-skill
|
|
7
|
+
assignableRoles:
|
|
8
|
+
- orchestrator
|
|
9
|
+
- team-leader
|
|
10
|
+
- developer
|
|
11
|
+
- operations
|
|
12
|
+
- ops
|
|
13
|
+
- sales
|
|
14
|
+
- support
|
|
15
|
+
- generalist
|
|
16
|
+
triggers:
|
|
17
|
+
- list calendar
|
|
18
|
+
- check calendar
|
|
19
|
+
- what's on my calendar
|
|
20
|
+
- upcoming meetings
|
|
21
|
+
tags:
|
|
22
|
+
- google
|
|
23
|
+
- calendar
|
|
24
|
+
execution:
|
|
25
|
+
type: script
|
|
26
|
+
script:
|
|
27
|
+
file: execute.sh
|
|
28
|
+
interpreter: bash
|
|
29
|
+
timeoutMs: 30000
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Calendar List
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bash execute.sh # next 7 days, primary calendar
|
|
36
|
+
bash execute.sh --from 2026-09-18T00:00:00Z --to 2026-09-19T00:00:00Z --max 20
|
|
37
|
+
bash execute.sh '{"calendarId":"team@group.calendar.google.com","from":"…","to":"…"}'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Parameters
|
|
41
|
+
|
|
42
|
+
| Name | Required | Meaning |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `--from` (`from`) | no | ISO 8601 lower bound; default now |
|
|
45
|
+
| `--to` (`to`) | no | ISO 8601 upper bound; default now + 7 days |
|
|
46
|
+
| `--calendar` (`calendarId`) | no | Calendar id; default `primary` |
|
|
47
|
+
| `--max` (`max`) | no | Result cap, default 50, max 250 |
|
|
48
|
+
|
|
49
|
+
## Output
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{"count":2,"events":[{"id":"…","summary":"Standup","start":{"dateTime":"2026-09-18T09:00:00+08:00"},"end":{"dateTime":"…"},"attendees":[{"email":"ann@example.com","responseStatus":"accepted"}],"htmlLink":"https://…"}]}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Failures
|
|
56
|
+
|
|
57
|
+
`{"success":false,"reason":"not_connected","hint":"<connect URL>"}` (exit 1)
|
|
58
|
+
when the owner has not connected Google Workspace.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# calendar-list — Upcoming events on the owner's Google Calendar (read-only)
|
|
4
|
+
#
|
|
5
|
+
# Backed by GET /api/google/calendar/events?from=&to=&calendarId=&max=.
|
|
6
|
+
# Without --from/--to the window is now → now + 7 days.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# bash execute.sh [--from ISO] [--to ISO] [--calendar id] [--max 20]
|
|
10
|
+
# bash execute.sh '{"from":"…","to":"…","calendarId":"primary","max":20}'
|
|
11
|
+
# =============================================================================
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
source "${SCRIPT_DIR}/../../_common/lib.sh"
|
|
15
|
+
|
|
16
|
+
print_usage() {
|
|
17
|
+
cat <<'EOF_USAGE'
|
|
18
|
+
Usage:
|
|
19
|
+
bash execute.sh [--from ISO-8601] [--to ISO-8601] [--calendar <id>] [--max 20]
|
|
20
|
+
bash execute.sh '{"from":"…","to":"…","calendarId":"primary","max":20}'
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--from Lower bound (default: now)
|
|
24
|
+
--to Upper bound (default: now + 7 days)
|
|
25
|
+
--calendar Calendar id (default: primary)
|
|
26
|
+
--max Result cap (default 50, max 250)
|
|
27
|
+
--help | -h Show this help
|
|
28
|
+
EOF_USAGE
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
DEFAULT_WINDOW_DAYS="${CREWLY_CALENDAR_LIST_DAYS:-7}"
|
|
32
|
+
|
|
33
|
+
# Portable "now + N days" in UTC ISO 8601 (macOS `date -v`, GNU `date -d`).
|
|
34
|
+
iso_now_plus_days() {
|
|
35
|
+
local days="$1"
|
|
36
|
+
if date -u -v+"${days}"d +%Y-%m-%dT%H:%M:%SZ >/dev/null 2>&1; then
|
|
37
|
+
date -u -v+"${days}"d +%Y-%m-%dT%H:%M:%SZ
|
|
38
|
+
else
|
|
39
|
+
date -u -d "+${days} days" +%Y-%m-%dT%H:%M:%SZ
|
|
40
|
+
fi
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
INPUT_JSON=""
|
|
44
|
+
FROM=""; TO=""; CALENDAR=""; MAX=""
|
|
45
|
+
|
|
46
|
+
if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
47
|
+
INPUT_JSON="$1"
|
|
48
|
+
shift || true
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
while [[ $# -gt 0 ]]; do
|
|
52
|
+
case "$1" in
|
|
53
|
+
--from) [ $# -ge 2 ] || error_exit "--from requires a value"; FROM="$2"; shift 2 ;;
|
|
54
|
+
--to) [ $# -ge 2 ] || error_exit "--to requires a value"; TO="$2"; shift 2 ;;
|
|
55
|
+
--calendar|-c) [ $# -ge 2 ] || error_exit "--calendar requires a value"; CALENDAR="$2"; shift 2 ;;
|
|
56
|
+
--max|-n) [ $# -ge 2 ] || error_exit "--max requires a value"; MAX="$2"; shift 2 ;;
|
|
57
|
+
--help|-h) print_usage; exit 0 ;;
|
|
58
|
+
*) error_exit "Unknown option: $1" ;;
|
|
59
|
+
esac
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
if [ -n "$INPUT_JSON" ]; then
|
|
63
|
+
INPUT=$(read_json_input "$INPUT_JSON")
|
|
64
|
+
[ -z "$FROM" ] && FROM=$(printf '%s' "$INPUT" | jq -r '.from // .timeMin // empty')
|
|
65
|
+
[ -z "$TO" ] && TO=$(printf '%s' "$INPUT" | jq -r '.to // .timeMax // empty')
|
|
66
|
+
[ -z "$CALENDAR" ] && CALENDAR=$(printf '%s' "$INPUT" | jq -r '.calendarId // .calendar // empty')
|
|
67
|
+
[ -z "$MAX" ] && MAX=$(printf '%s' "$INPUT" | jq -r '.max // empty')
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
[ -z "$FROM" ] && FROM=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
71
|
+
[ -z "$TO" ] && TO=$(iso_now_plus_days "$DEFAULT_WINDOW_DAYS")
|
|
72
|
+
|
|
73
|
+
QS="from=$(jq -rn --arg v "$FROM" '$v|@uri')&to=$(jq -rn --arg v "$TO" '$v|@uri')"
|
|
74
|
+
[ -n "$CALENDAR" ] && QS="${QS}&calendarId=$(jq -rn --arg v "$CALENDAR" '$v|@uri')"
|
|
75
|
+
[ -n "$MAX" ] && QS="${QS}&max=$(jq -rn --arg v "$MAX" '$v|@uri')"
|
|
76
|
+
|
|
77
|
+
RESPONSE=$(api_call GET "/google/calendar/events?${QS}" 2>&1) || {
|
|
78
|
+
printf '%s' "$RESPONSE" | jq -c '{success: false, reason: (.details.error // .details // .error // "unknown"), hint: (.details.hint // ""), message: (.details.message // "")}' 2>/dev/null \
|
|
79
|
+
|| jq -n --arg r "$RESPONSE" '{success: false, reason: $r}'
|
|
80
|
+
exit 1
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Oversized bodies are parked on disk by api_call; pass that envelope through.
|
|
84
|
+
if printf '%s' "$RESPONSE" | jq -e '.truncated == true' >/dev/null 2>&1; then
|
|
85
|
+
printf '%s\n' "$RESPONSE"; exit 0
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
printf '%s' "$RESPONSE" | jq -c '{count: .data.count, events: .data.events}'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for calendar-list — run with: bash execute.test.sh
|
|
3
|
+
# Spins up a python HTTP stub as the backend, asserts the request path the
|
|
4
|
+
# skill sends and the JSON it prints. Exit 0 on pass.
|
|
5
|
+
set -u
|
|
6
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
EXEC="$HERE/execute.sh"
|
|
8
|
+
PASS=0; FAIL=0
|
|
9
|
+
check() {
|
|
10
|
+
local name="$1"; local got="$2"; local want="$3"
|
|
11
|
+
if [ "$got" = "$want" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $name"; echo " want: $want"; echo " got: $got"; fi
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
PORT=18804
|
|
15
|
+
STUB_LOG="$(mktemp)"; export STUB_LOG; export STUB_PORT="$PORT"
|
|
16
|
+
python3 -u <<'PY' >/dev/null 2>&1 &
|
|
17
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
18
|
+
from urllib.parse import urlparse, parse_qs
|
|
19
|
+
import json, os
|
|
20
|
+
LOG = os.environ['STUB_LOG']; PORT = int(os.environ['STUB_PORT'])
|
|
21
|
+
EV = {'id': 'e1', 'summary': 'Standup', 'start': {'dateTime': '2026-09-18T09:00:00Z'}, 'end': {'dateTime': '2026-09-18T09:15:00Z'}, 'attendees': []}
|
|
22
|
+
class H(BaseHTTPRequestHandler):
|
|
23
|
+
def do_GET(self):
|
|
24
|
+
open(LOG, 'w').write(json.dumps({'method': 'GET', 'path': self.path}))
|
|
25
|
+
qs = parse_qs(urlparse(self.path).query)
|
|
26
|
+
if qs.get('calendarId', [''])[0] == 'no-grant':
|
|
27
|
+
self.send_response(409); self.send_header('Content-Type', 'application/json'); self.end_headers()
|
|
28
|
+
self.wfile.write(json.dumps({'success': False, 'error': 'not_connected', 'message': 'no grant', 'hint': 'https://cloud/start?token=j'}).encode()); return
|
|
29
|
+
self.send_response(200); self.send_header('Content-Type', 'application/json'); self.end_headers()
|
|
30
|
+
self.wfile.write(json.dumps({'success': True, 'data': {'count': 1, 'events': [EV]}}).encode())
|
|
31
|
+
def log_message(self, *a, **k): pass
|
|
32
|
+
HTTPServer(('127.0.0.1', PORT), H).serve_forever()
|
|
33
|
+
PY
|
|
34
|
+
STUB_PID=$!
|
|
35
|
+
disown "$STUB_PID" 2>/dev/null || true
|
|
36
|
+
trap 'kill "$STUB_PID" >/dev/null 2>&1; rm -f "$STUB_LOG"' EXIT
|
|
37
|
+
for i in $(seq 1 30); do curl -s -o /dev/null -m 0.2 "http://127.0.0.1:${PORT}/" 2>/dev/null && break; sleep 0.1; done
|
|
38
|
+
|
|
39
|
+
run() { CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" "$@" 2>/dev/null; }
|
|
40
|
+
run_err() { CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" "$@" 2>&1 >/dev/null | jq -r '.error' 2>/dev/null; }
|
|
41
|
+
|
|
42
|
+
WANT='{"count":1,"events":[{"id":"e1","summary":"Standup","start":{"dateTime":"2026-09-18T09:00:00Z"},"end":{"dateTime":"2026-09-18T09:15:00Z"},"attendees":[]}]}'
|
|
43
|
+
|
|
44
|
+
OUT=$(run --from 2026-09-18T00:00:00Z --to 2026-09-19T00:00:00Z --calendar "team@group.calendar.google.com" --max 20)
|
|
45
|
+
check "flags: output" "$OUT" "$WANT"
|
|
46
|
+
check "flags: path" "$(jq -r .path "$STUB_LOG")" "/api/google/calendar/events?from=2026-09-18T00%3A00%3A00Z&to=2026-09-19T00%3A00%3A00Z&calendarId=team%40group.calendar.google.com&max=20"
|
|
47
|
+
|
|
48
|
+
OUT=$(run '{"from":"2026-09-18T00:00:00Z","to":"2026-09-19T00:00:00Z"}')
|
|
49
|
+
check "json: output" "$OUT" "$WANT"
|
|
50
|
+
check "json: path without optional params" "$(jq -r .path "$STUB_LOG")" "/api/google/calendar/events?from=2026-09-18T00%3A00%3A00Z&to=2026-09-19T00%3A00%3A00Z"
|
|
51
|
+
|
|
52
|
+
# --- defaults: now → now + 7 days (shape only; values are wall-clock) ---
|
|
53
|
+
run >/dev/null
|
|
54
|
+
P=$(jq -r .path "$STUB_LOG")
|
|
55
|
+
FROM_V=$(printf '%s' "$P" | sed -E 's/.*[?&]from=([^&]*).*/\1/')
|
|
56
|
+
TO_V=$(printf '%s' "$P" | sed -E 's/.*[?&]to=([^&]*).*/\1/')
|
|
57
|
+
[[ "$FROM_V" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}%3A[0-9]{2}%3A[0-9]{2}Z$ ]] && FROM_OK=yes || FROM_OK=no
|
|
58
|
+
[[ "$TO_V" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}%3A[0-9]{2}%3A[0-9]{2}Z$ ]] && TO_OK=yes || TO_OK=no
|
|
59
|
+
check "defaults: from is ISO UTC" "$FROM_OK" "yes"
|
|
60
|
+
check "defaults: to is ISO UTC" "$TO_OK" "yes"
|
|
61
|
+
[ "$TO_V" \> "$FROM_V" ] && ORDER=yes || ORDER=no
|
|
62
|
+
check "defaults: to is after from" "$ORDER" "yes"
|
|
63
|
+
|
|
64
|
+
OUT=$(CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" --calendar no-grant 2>/dev/null); RC=$?
|
|
65
|
+
check "409: exit code" "$RC" "1"
|
|
66
|
+
check "409: reason + hint" "$OUT" '{"success":false,"reason":"not_connected","hint":"https://cloud/start?token=j","message":"no grant"}'
|
|
67
|
+
|
|
68
|
+
check "unknown option errors" "$(run_err --bogus 1)" "Unknown option: --bogus"
|
|
69
|
+
|
|
70
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
71
|
+
[ "$FAIL" -eq 0 ]
|