@enterpriseai/cli 3.19.0 → 3.19.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/package.json +3 -3
- package/resources/deploy/eai-app-template/producer-pin.json +5 -5
- package/resources/deploy/eai-app-template/tests/source-unknown-deployment-evidence.test.mjs +2141 -1
- package/resources/gofer/.gofer-version +4 -357
- package/resources/gofer/agents-skills/eai/SKILL.md +365 -0
- package/resources/gofer/agents-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/agents-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/bash-scripts/create-new-feature.sh +3 -1
- package/resources/gofer/bash-scripts/sync-implementation-status.sh +4 -3
- package/resources/gofer/bash-scripts/update-agent-context.sh +7 -8
- package/resources/gofer/bash-scripts/validate-artifact.sh +10 -6
- package/resources/gofer/bash-scripts/verify-object-type-routing-workspace.sh +184 -0
- package/resources/gofer/claude-commands/eai-update.md +46 -0
- package/resources/gofer/claude-commands/eai.md +355 -0
- package/resources/gofer/claude-skills/eai/SKILL.md +365 -0
- package/resources/gofer/claude-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/claude-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/commands/0_gofer_start.md +440 -99
- package/resources/gofer/commands/0a_problem_validation.md +187 -27
- package/resources/gofer/commands/10_gofer_cloud.md +183 -23
- package/resources/gofer/commands/1_gofer_research.md +239 -30
- package/resources/gofer/commands/2_gofer_specify.md +239 -35
- package/resources/gofer/commands/3_gofer_plan.md +259 -43
- package/resources/gofer/commands/4_gofer_tasks.md +401 -81
- package/resources/gofer/commands/5_gofer_implement.md +375 -84
- package/resources/gofer/commands/6_gofer_validate.md +284 -53
- package/resources/gofer/commands/7_gofer_save.md +187 -26
- package/resources/gofer/commands/7a_stakeholder_comms.md +183 -23
- package/resources/gofer/commands/8_gofer_branding.md +183 -23
- package/resources/gofer/commands/9_gofer_tests.md +212 -23
- package/resources/gofer/commands/gofer_bootstrap_workspace.md +121 -8
- package/resources/gofer/commands/gofer_check_workspace.md +123 -8
- package/resources/gofer/commands/gofer_constitution.md +183 -23
- package/resources/gofer/commands/gofer_diagnose.md +183 -23
- package/resources/gofer/commands/gofer_eai_first_run.md +254 -25
- package/resources/gofer/commands/gofer_hydrate.md +183 -23
- package/resources/gofer/commands/gofer_personality.md +117 -6
- package/resources/gofer/commands/gofer_plan.md +117 -6
- package/resources/gofer/commands/gofer_side.md +117 -6
- package/resources/gofer/commands/gofer_spec_summary.md +183 -23
- package/resources/gofer/commands/gofer_tdd.md +183 -23
- package/resources/gofer/commands/gofer_vocabulary.md +185 -25
- package/resources/gofer/commands/gofer_zoom_out.md +183 -23
- package/resources/gofer/config/typesafe-learning-review.json +16 -0
- package/resources/gofer/config/typesafe-semantic-review.json +9 -0
- package/resources/gofer/copilot-instructions/gofer-cost.instructions.md +15 -6
- package/resources/gofer/copilot-instructions/typescript.instructions.md +1 -1
- package/resources/gofer/copilot-prompts/eai-update.prompt.md +58 -0
- package/resources/gofer/copilot-prompts/eai.prompt.md +367 -0
- package/resources/gofer/gemini/commands/gofer/eai-update.md +22 -0
- package/resources/gofer/gemini/commands/gofer/eai-update.toml +2 -0
- package/resources/gofer/gemini/commands/gofer/eai.md +357 -0
- package/resources/gofer/gemini/commands/gofer/eai.toml +2 -0
- package/resources/gofer/gemini/commands/gofer/manifest.json +3 -27
- package/resources/gofer/gemini/extension.json +4 -4
- package/resources/gofer/github-agents/eai.agent.md +83 -0
- package/resources/gofer/github-agents/gofer-business.agent.md +73 -4
- package/resources/gofer/github-agents/gofer-implement.agent.md +71 -2
- package/resources/gofer/github-agents/gofer-plan.agent.md +72 -2
- package/resources/gofer/github-agents/gofer-research.agent.md +72 -2
- package/resources/gofer/github-agents/gofer-validate.agent.md +72 -2
- package/resources/gofer/github-skills/eai/SKILL.md +365 -0
- package/resources/gofer/github-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/github-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/grok-skills/eai/SKILL.md +267 -14
- package/resources/gofer/grok-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/hook-scripts/user-prompt-submit.mjs +58 -13
- package/resources/gofer/instruction-templates/base/agents-base.md +8 -4
- package/resources/gofer/instruction-templates/gofer/gofer-claude.md +7 -5
- package/resources/gofer/instruction-templates/gofer/gofer-copilot.md +10 -8
- package/resources/gofer/memory/constitution.md +9 -7
- package/resources/gofer/memory/gofer-model-policy.yaml +23 -1
- package/resources/gofer/memory/lessons.md +0 -31
- package/resources/gofer/node-scripts/canonical-descriptions.mjs +1 -1
- package/resources/gofer/node-scripts/check-version-alignment.mjs +4 -7
- package/resources/gofer/node-scripts/delivery-lineage-corpus.mjs +156 -0
- package/resources/gofer/node-scripts/generate-commands.mjs +1073 -265
- package/resources/gofer/node-scripts/gofer-acceptance-check.mjs +143 -0
- package/resources/gofer/node-scripts/gofer-agent-catalog.mjs +271 -0
- package/resources/gofer/node-scripts/gofer-benchmark-executor.mjs +123 -0
- package/resources/gofer/node-scripts/gofer-benchmark-sign.mjs +62 -0
- package/resources/gofer/node-scripts/gofer-benchmark-signer.mjs +89 -0
- package/resources/gofer/node-scripts/gofer-benchmark.mjs +76 -0
- package/resources/gofer/node-scripts/gofer-blocker-control.mjs +274 -0
- package/resources/gofer/node-scripts/gofer-claude-adapter.mjs +154 -0
- package/resources/gofer/node-scripts/gofer-closed-loop-audit.mjs +19 -2
- package/resources/gofer/node-scripts/gofer-delivery-check.mjs +121 -0
- package/resources/gofer/node-scripts/gofer-encrypted-key.mjs +58 -0
- package/resources/gofer/node-scripts/gofer-execution-metrics.mjs +477 -0
- package/resources/gofer/node-scripts/gofer-execution-recovery.mjs +334 -0
- package/resources/gofer/node-scripts/gofer-grok-adapter.mjs +184 -0
- package/resources/gofer/node-scripts/gofer-heldout-corpus.mjs +73 -0
- package/resources/gofer/node-scripts/gofer-heldout-snapshot.mjs +286 -0
- package/resources/gofer/node-scripts/gofer-heldout-verifier.mjs +211 -0
- package/resources/gofer/node-scripts/gofer-host-capability.mjs +335 -0
- package/resources/gofer/node-scripts/gofer-independent-reviewer.mjs +130 -0
- package/resources/gofer/node-scripts/gofer-live-routing.mjs +109 -0
- package/resources/gofer/node-scripts/gofer-local-capability-issuer.mjs +71 -0
- package/resources/gofer/node-scripts/gofer-local-isolation.mjs +175 -0
- package/resources/gofer/node-scripts/gofer-local-settings-cleanup.mjs +388 -0
- package/resources/gofer/node-scripts/gofer-loop-audit.mjs +54 -3
- package/resources/gofer/node-scripts/gofer-native-adapter.mjs +567 -0
- package/resources/gofer/node-scripts/gofer-native-benchmark-dispatch.mjs +145 -0
- package/resources/gofer/node-scripts/gofer-native-runtime.mjs +169 -0
- package/resources/gofer/node-scripts/gofer-pre-edit-check.mjs +204 -0
- package/resources/gofer/node-scripts/gofer-priority-check.mjs +214 -0
- package/resources/gofer/node-scripts/gofer-response-check.mjs +56 -0
- package/resources/gofer/node-scripts/gofer-reviewed-learning.mjs +1232 -0
- package/resources/gofer/node-scripts/gofer-run-verified-task.mjs +330 -0
- package/resources/gofer/node-scripts/gofer-runtime-ledger.mjs +289 -0
- package/resources/gofer/node-scripts/gofer-semantic-drift.mjs +584 -0
- package/resources/gofer/node-scripts/gofer-surface-update.mjs +1178 -0
- package/resources/gofer/node-scripts/gofer-trust-bootstrap.mjs +77 -0
- package/resources/gofer/node-scripts/gofer-trusted-benchmark.mjs +102 -0
- package/resources/gofer/node-scripts/gofer-trusted-evaluator.mjs +274 -0
- package/resources/gofer/node-scripts/gofer-tty-prompt.mjs +34 -0
- package/resources/gofer/node-scripts/gofer-typesafe-credentials.mjs +435 -0
- package/resources/gofer/node-scripts/gofer-ui-preview.mjs +1212 -0
- package/resources/gofer/node-scripts/gofer-verified-execution.mjs +484 -0
- package/resources/gofer/node-scripts/gofer-verifier-key-ceremony.mjs +101 -0
- package/resources/gofer/node-scripts/gofer-worktree-layout.mjs +32 -0
- package/resources/gofer/node-scripts/object-type-routing-phase-bundle.mjs +360 -0
- package/resources/gofer/node-scripts/package-agent-plugin.mjs +574 -82
- package/resources/gofer/node-scripts/parse-stage-command.mjs +1 -1
- package/resources/gofer/node-scripts/render-delivery-lineage.mjs +178 -0
- package/resources/gofer/node-scripts/sync-extension-resources.mjs +78 -2
- package/resources/gofer/node-scripts/validate-object-type-identifiers.mjs +767 -0
- package/resources/gofer/node-scripts/validate-object-type-routing-workspace.mjs +1325 -0
- package/resources/gofer/node-scripts/validate-v4-resource-contract.d.mts +44 -0
- package/resources/gofer/node-scripts/validate-v4-resource-contract.mjs +957 -0
- package/resources/gofer/node-scripts/workspace-bootstrap-lib.mjs +685 -116
- package/resources/gofer/references/agent-catalog.json +126 -0
- package/resources/gofer/references/blocker-mediation.md +156 -0
- package/resources/gofer/references/business-updates-and-goal-checks.md +109 -0
- package/resources/gofer/references/delivery-lineage.md +48 -0
- package/resources/gofer/references/mvp-capability-validation.md +102 -0
- package/resources/gofer/references/platform/README.md +1 -1
- package/resources/gofer/references/platform/eai-app-template.md +15 -13
- package/resources/gofer/references/platform/eai-auth-access.md +104 -0
- package/resources/gofer/references/platform/eai-config-driven-ui.md +5 -5
- package/resources/gofer/references/platform/eai-error-catalog.yaml +77 -19
- package/resources/gofer/references/platform/eai-repo-contract.md +61 -23
- package/resources/gofer/references/platform/eai-service-patterns.md +102 -105
- package/resources/gofer/references/platform/eai.md +44 -8
- package/resources/gofer/references/platform/vertical-template.md +10 -9
- package/resources/gofer/references/priority-outcome-protection.md +152 -0
- package/resources/gofer/references/verified-agent-execution.md +179 -0
- package/resources/gofer/schemas/gofer-learning-trace-v1.json +70 -0
- package/resources/gofer/specify-commands/0_gofer_start.md +440 -99
- package/resources/gofer/specify-commands/0a_problem_validation.md +187 -27
- package/resources/gofer/specify-commands/10_gofer_cloud.md +183 -23
- package/resources/gofer/specify-commands/1_gofer_research.md +239 -30
- package/resources/gofer/specify-commands/2_gofer_specify.md +239 -35
- package/resources/gofer/specify-commands/3_gofer_plan.md +259 -43
- package/resources/gofer/specify-commands/4_gofer_tasks.md +401 -81
- package/resources/gofer/specify-commands/5_gofer_implement.md +375 -84
- package/resources/gofer/specify-commands/6_gofer_validate.md +284 -53
- package/resources/gofer/specify-commands/7_gofer_save.md +187 -26
- package/resources/gofer/specify-commands/7a_stakeholder_comms.md +183 -23
- package/resources/gofer/specify-commands/8_gofer_branding.md +183 -23
- package/resources/gofer/specify-commands/9_gofer_tests.md +212 -23
- package/resources/gofer/specify-commands/gofer_bootstrap_workspace.md +121 -8
- package/resources/gofer/specify-commands/gofer_check_workspace.md +123 -8
- package/resources/gofer/specify-commands/gofer_constitution.md +183 -23
- package/resources/gofer/specify-commands/gofer_diagnose.md +183 -23
- package/resources/gofer/specify-commands/gofer_eai_first_run.md +254 -25
- package/resources/gofer/specify-commands/gofer_hydrate.md +183 -23
- package/resources/gofer/specify-commands/gofer_personality.md +117 -6
- package/resources/gofer/specify-commands/gofer_plan.md +117 -6
- package/resources/gofer/specify-commands/gofer_side.md +117 -6
- package/resources/gofer/specify-commands/gofer_spec_summary.md +183 -23
- package/resources/gofer/specify-commands/gofer_tdd.md +183 -23
- package/resources/gofer/specify-commands/gofer_vocabulary.md +185 -25
- package/resources/gofer/specify-commands/gofer_zoom_out.md +183 -23
- package/resources/gofer/specify-config/object-type-routing.json +24 -0
- package/resources/gofer/specify-config/typesafe-learning-review.json +16 -0
- package/resources/gofer/specify-config/typesafe-semantic-review.json +9 -0
- package/resources/gofer/system-skills/eai/SKILL.md +365 -0
- package/resources/gofer/system-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/system-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/templates/build-map-template.md +64 -0
- package/resources/gofer/templates/business-scenarios-template.json +14 -0
- package/resources/gofer/templates/ciso-security-summary-template.md +3 -3
- package/resources/gofer/templates/context-bundle-template.md +4 -3
- package/resources/gofer/templates/contract-pack-template.md +8 -8
- package/resources/gofer/templates/cto-architecture-summary-template.md +3 -3
- package/resources/gofer/templates/delivery-lineage-template.json +8 -0
- package/resources/gofer/templates/eai-preflight-template.md +109 -68
- package/resources/gofer/templates/goal-ledger-template.json +1 -1
- package/resources/gofer/templates/gofer-model-policy.yaml +28 -6
- package/resources/gofer/templates/loop-contract-template.json +3 -1
- package/resources/gofer/templates/plan-template.md +8 -7
- package/resources/gofer/templates/priority-plan-template.json +29 -0
- package/resources/gofer/templates/proposal-review-template.md +6 -3
- package/resources/gofer/templates/release-capability-ledger-template.md +24 -0
- package/resources/gofer/templates/research-template.md +7 -6
- package/resources/gofer/templates/spec-summary-template.md +1 -1
- package/resources/gofer/templates/spec-template.md +45 -6
- package/resources/gofer/templates/tasks-template.md +12 -3
- package/resources/gofer/templates/ui-preview-brief-template.md +55 -5
- package/resources/gofer/templates/ui-review-log-template.md +21 -3
- package/resources/gofer/templates/ui-show-and-tell-template.md +46 -0
- package/resources/gofer/templates/visuals/capability-heatmap-template.md +2 -2
- package/resources/gofer/templates/working-backwards-prfaq-template.md +9 -8
- package/resources/linked-sources.json +4 -4
- package/resources/gofer/agents-skills/0_gofer_start/SKILL.md +0 -1287
- package/resources/gofer/agents-skills/0a_problem_validation/SKILL.md +0 -429
- package/resources/gofer/agents-skills/10_gofer_cloud/SKILL.md +0 -635
- package/resources/gofer/agents-skills/1_gofer_research/SKILL.md +0 -1159
- package/resources/gofer/agents-skills/2_gofer_specify/SKILL.md +0 -860
- package/resources/gofer/agents-skills/3_gofer_plan/SKILL.md +0 -899
- package/resources/gofer/agents-skills/4_gofer_tasks/SKILL.md +0 -760
- package/resources/gofer/agents-skills/5_gofer_implement/SKILL.md +0 -944
- package/resources/gofer/agents-skills/6_gofer_validate/SKILL.md +0 -2302
- package/resources/gofer/agents-skills/7_gofer_save/SKILL.md +0 -423
- package/resources/gofer/agents-skills/7a_stakeholder_comms/SKILL.md +0 -508
- package/resources/gofer/agents-skills/8_gofer_branding/SKILL.md +0 -315
- package/resources/gofer/agents-skills/9_gofer_tests/SKILL.md +0 -558
- package/resources/gofer/agents-skills/gofer_bootstrap_workspace/SKILL.md +0 -148
- package/resources/gofer/agents-skills/gofer_check_workspace/SKILL.md +0 -119
- package/resources/gofer/agents-skills/gofer_constitution/SKILL.md +0 -612
- package/resources/gofer/agents-skills/gofer_diagnose/SKILL.md +0 -114
- package/resources/gofer/agents-skills/gofer_eai_first_run/SKILL.md +0 -397
- package/resources/gofer/agents-skills/gofer_hydrate/SKILL.md +0 -561
- package/resources/gofer/agents-skills/gofer_personality/SKILL.md +0 -44
- package/resources/gofer/agents-skills/gofer_plan/SKILL.md +0 -40
- package/resources/gofer/agents-skills/gofer_side/SKILL.md +0 -45
- package/resources/gofer/agents-skills/gofer_spec_summary/SKILL.md +0 -112
- package/resources/gofer/agents-skills/gofer_tdd/SKILL.md +0 -112
- package/resources/gofer/agents-skills/gofer_vocabulary/SKILL.md +0 -112
- package/resources/gofer/agents-skills/gofer_zoom_out/SKILL.md +0 -112
- package/resources/gofer/claude-commands/0_gofer_start.md +0 -1282
- package/resources/gofer/claude-commands/0a_problem_validation.md +0 -424
- package/resources/gofer/claude-commands/10_gofer_cloud.md +0 -630
- package/resources/gofer/claude-commands/1_gofer_research.md +0 -1154
- package/resources/gofer/claude-commands/2_gofer_specify.md +0 -855
- package/resources/gofer/claude-commands/3_gofer_plan.md +0 -894
- package/resources/gofer/claude-commands/4_gofer_tasks.md +0 -755
- package/resources/gofer/claude-commands/5_gofer_implement.md +0 -939
- package/resources/gofer/claude-commands/6_gofer_validate.md +0 -2297
- package/resources/gofer/claude-commands/7_gofer_save.md +0 -418
- package/resources/gofer/claude-commands/7a_stakeholder_comms.md +0 -503
- package/resources/gofer/claude-commands/8_gofer_branding.md +0 -310
- package/resources/gofer/claude-commands/9_gofer_tests.md +0 -553
- package/resources/gofer/claude-commands/gofer_bootstrap_workspace.md +0 -143
- package/resources/gofer/claude-commands/gofer_check_workspace.md +0 -114
- package/resources/gofer/claude-commands/gofer_constitution.md +0 -607
- package/resources/gofer/claude-commands/gofer_diagnose.md +0 -109
- package/resources/gofer/claude-commands/gofer_eai_first_run.md +0 -392
- package/resources/gofer/claude-commands/gofer_hydrate.md +0 -556
- package/resources/gofer/claude-commands/gofer_personality.md +0 -39
- package/resources/gofer/claude-commands/gofer_plan.md +0 -35
- package/resources/gofer/claude-commands/gofer_side.md +0 -40
- package/resources/gofer/claude-commands/gofer_spec_summary.md +0 -107
- package/resources/gofer/claude-commands/gofer_tdd.md +0 -107
- package/resources/gofer/claude-commands/gofer_vocabulary.md +0 -107
- package/resources/gofer/claude-commands/gofer_zoom_out.md +0 -107
- package/resources/gofer/claude-skills/eai-gofer/SKILL.md +0 -56
- package/resources/gofer/copilot-prompts/0_gofer_start.prompt.md +0 -1308
- package/resources/gofer/copilot-prompts/0a_problem_validation.prompt.md +0 -429
- package/resources/gofer/copilot-prompts/10_gofer_cloud.prompt.md +0 -644
- package/resources/gofer/copilot-prompts/1_gofer_research.prompt.md +0 -1177
- package/resources/gofer/copilot-prompts/2_gofer_specify.prompt.md +0 -878
- package/resources/gofer/copilot-prompts/3_gofer_plan.prompt.md +0 -917
- package/resources/gofer/copilot-prompts/4_gofer_tasks.prompt.md +0 -778
- package/resources/gofer/copilot-prompts/5_gofer_implement.prompt.md +0 -962
- package/resources/gofer/copilot-prompts/6_gofer_validate.prompt.md +0 -2310
- package/resources/gofer/copilot-prompts/7_gofer_save.prompt.md +0 -433
- package/resources/gofer/copilot-prompts/7a_stakeholder_comms.prompt.md +0 -516
- package/resources/gofer/copilot-prompts/8_gofer_branding.prompt.md +0 -323
- package/resources/gofer/copilot-prompts/9_gofer_tests.prompt.md +0 -566
- package/resources/gofer/copilot-prompts/gofer_bootstrap_workspace.prompt.md +0 -161
- package/resources/gofer/copilot-prompts/gofer_check_workspace.prompt.md +0 -132
- package/resources/gofer/copilot-prompts/gofer_constitution.prompt.md +0 -621
- package/resources/gofer/copilot-prompts/gofer_diagnose.prompt.md +0 -127
- package/resources/gofer/copilot-prompts/gofer_eai_first_run.prompt.md +0 -410
- package/resources/gofer/copilot-prompts/gofer_hydrate.prompt.md +0 -571
- package/resources/gofer/copilot-prompts/gofer_personality.prompt.md +0 -57
- package/resources/gofer/copilot-prompts/gofer_plan.prompt.md +0 -53
- package/resources/gofer/copilot-prompts/gofer_side.prompt.md +0 -58
- package/resources/gofer/copilot-prompts/gofer_spec_summary.prompt.md +0 -125
- package/resources/gofer/copilot-prompts/gofer_tdd.prompt.md +0 -125
- package/resources/gofer/copilot-prompts/gofer_vocabulary.prompt.md +0 -125
- package/resources/gofer/copilot-prompts/gofer_zoom_out.prompt.md +0 -125
- package/resources/gofer/gemini/commands/gofer/0_gofer_start.md +0 -1282
- package/resources/gofer/gemini/commands/gofer/0_gofer_start.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/0a_problem_validation.md +0 -424
- package/resources/gofer/gemini/commands/gofer/0a_problem_validation.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/10_gofer_cloud.md +0 -630
- package/resources/gofer/gemini/commands/gofer/10_gofer_cloud.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/1_gofer_research.md +0 -1154
- package/resources/gofer/gemini/commands/gofer/1_gofer_research.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/2_gofer_specify.md +0 -855
- package/resources/gofer/gemini/commands/gofer/2_gofer_specify.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/3_gofer_plan.md +0 -894
- package/resources/gofer/gemini/commands/gofer/3_gofer_plan.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/4_gofer_tasks.md +0 -755
- package/resources/gofer/gemini/commands/gofer/4_gofer_tasks.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/5_gofer_implement.md +0 -939
- package/resources/gofer/gemini/commands/gofer/5_gofer_implement.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/6_gofer_validate.md +0 -2297
- package/resources/gofer/gemini/commands/gofer/6_gofer_validate.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/7_gofer_save.md +0 -418
- package/resources/gofer/gemini/commands/gofer/7_gofer_save.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/7a_stakeholder_comms.md +0 -503
- package/resources/gofer/gemini/commands/gofer/7a_stakeholder_comms.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/8_gofer_branding.md +0 -310
- package/resources/gofer/gemini/commands/gofer/8_gofer_branding.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/9_gofer_tests.md +0 -553
- package/resources/gofer/gemini/commands/gofer/9_gofer_tests.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_bootstrap_workspace.md +0 -143
- package/resources/gofer/gemini/commands/gofer/gofer_bootstrap_workspace.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_check_workspace.md +0 -114
- package/resources/gofer/gemini/commands/gofer/gofer_check_workspace.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_constitution.md +0 -607
- package/resources/gofer/gemini/commands/gofer/gofer_constitution.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_diagnose.md +0 -109
- package/resources/gofer/gemini/commands/gofer/gofer_diagnose.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_eai_first_run.md +0 -392
- package/resources/gofer/gemini/commands/gofer/gofer_eai_first_run.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_hydrate.md +0 -556
- package/resources/gofer/gemini/commands/gofer/gofer_hydrate.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_personality.md +0 -39
- package/resources/gofer/gemini/commands/gofer/gofer_personality.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_plan.md +0 -35
- package/resources/gofer/gemini/commands/gofer/gofer_plan.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_side.md +0 -40
- package/resources/gofer/gemini/commands/gofer/gofer_side.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_spec_summary.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_spec_summary.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_tdd.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_tdd.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_vocabulary.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_vocabulary.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_zoom_out.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_zoom_out.toml +0 -2
- package/resources/gofer/github-skills/eai-gofer/SKILL.md +0 -56
- package/resources/gofer/system-skills/0_gofer_start/SKILL.md +0 -1287
- package/resources/gofer/system-skills/0a_problem_validation/SKILL.md +0 -429
- package/resources/gofer/system-skills/10_gofer_cloud/SKILL.md +0 -635
- package/resources/gofer/system-skills/1_gofer_research/SKILL.md +0 -1159
- package/resources/gofer/system-skills/2_gofer_specify/SKILL.md +0 -860
- package/resources/gofer/system-skills/3_gofer_plan/SKILL.md +0 -899
- package/resources/gofer/system-skills/4_gofer_tasks/SKILL.md +0 -760
- package/resources/gofer/system-skills/5_gofer_implement/SKILL.md +0 -944
- package/resources/gofer/system-skills/6_gofer_validate/SKILL.md +0 -2302
- package/resources/gofer/system-skills/7_gofer_save/SKILL.md +0 -423
- package/resources/gofer/system-skills/7a_stakeholder_comms/SKILL.md +0 -508
- package/resources/gofer/system-skills/8_gofer_branding/SKILL.md +0 -315
- package/resources/gofer/system-skills/9_gofer_tests/SKILL.md +0 -558
- package/resources/gofer/system-skills/gofer_bootstrap_workspace/SKILL.md +0 -148
- package/resources/gofer/system-skills/gofer_check_workspace/SKILL.md +0 -119
- package/resources/gofer/system-skills/gofer_constitution/SKILL.md +0 -612
- package/resources/gofer/system-skills/gofer_diagnose/SKILL.md +0 -114
- package/resources/gofer/system-skills/gofer_eai_first_run/SKILL.md +0 -397
- package/resources/gofer/system-skills/gofer_hydrate/SKILL.md +0 -561
- package/resources/gofer/system-skills/gofer_personality/SKILL.md +0 -44
- package/resources/gofer/system-skills/gofer_plan/SKILL.md +0 -40
- package/resources/gofer/system-skills/gofer_side/SKILL.md +0 -45
- package/resources/gofer/system-skills/gofer_spec_summary/SKILL.md +0 -112
- package/resources/gofer/system-skills/gofer_tdd/SKILL.md +0 -112
- package/resources/gofer/system-skills/gofer_vocabulary/SKILL.md +0 -112
- package/resources/gofer/system-skills/gofer_zoom_out/SKILL.md +0 -112
- package/resources/gofer/templates/ui-approval-template.md +0 -32
|
@@ -0,0 +1,1178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plans and runs user-level Gofer install and update actions for supported hosts.
|
|
5
|
+
* This script is intentionally independent of a repository scaffold.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execFile } from 'node:child_process';
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
10
|
+
import { constants as fsConstants, promises as fs } from 'node:fs';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { promisify } from 'node:util';
|
|
15
|
+
import { cleanupLocalSettings } from './gofer-local-settings-cleanup.mjs';
|
|
16
|
+
|
|
17
|
+
const execFileAsync = promisify(execFile);
|
|
18
|
+
const REPOSITORY_URL = 'https://github.com/eai-support/eai-gofer';
|
|
19
|
+
const VS_CODE_EXTENSION_ID = 'EnterpriseAI.gofer';
|
|
20
|
+
export const SUPPORTED_HOSTS = Object.freeze([
|
|
21
|
+
'claude',
|
|
22
|
+
'codex',
|
|
23
|
+
'copilot',
|
|
24
|
+
'antigravity',
|
|
25
|
+
'grok',
|
|
26
|
+
'vscode',
|
|
27
|
+
]);
|
|
28
|
+
export const INVOCATION_PREFIXES = Object.freeze({
|
|
29
|
+
claude: '/',
|
|
30
|
+
codex: '$',
|
|
31
|
+
copilot: '/',
|
|
32
|
+
antigravity: '/',
|
|
33
|
+
grok: '/',
|
|
34
|
+
vscode: '/',
|
|
35
|
+
});
|
|
36
|
+
const LEGACY_HOST_ALIASES = Object.freeze({
|
|
37
|
+
gemini: 'antigravity',
|
|
38
|
+
});
|
|
39
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
40
|
+
const DEFAULT_PLUGIN_ROOT = path.resolve(path.dirname(scriptPath), '..', '..', '..');
|
|
41
|
+
const ALWAYS_ON_EAI_START = '<!-- gofer:always-on-eai:start -->';
|
|
42
|
+
const ALWAYS_ON_EAI_END = '<!-- gofer:always-on-eai:end -->';
|
|
43
|
+
const ALWAYS_ON_EAI_SECTION = `## Always-On EAI Contract
|
|
44
|
+
${ALWAYS_ON_EAI_START}
|
|
45
|
+
|
|
46
|
+
Apply Gofer to every request. The user does not need to type \`/eai\` or \`$eai\`.
|
|
47
|
+
|
|
48
|
+
1. Preserve the user's request. Do not add a visible command prefix.
|
|
49
|
+
2. Use Gofer's internal routing. Do not make the user choose a pipeline stage.
|
|
50
|
+
3. Use concise, business-first ASD-STE100 style.
|
|
51
|
+
4. Check workspace health before meaningful repo work, tool use, or a pipeline stage. Do not repeat setup on every message.
|
|
52
|
+
5. Use Gofer maintenance only when the user explicitly asks to install or update Gofer.
|
|
53
|
+
${ALWAYS_ON_EAI_END}`;
|
|
54
|
+
const ALWAYS_ON_EAI_MARKER = /## Always-On EAI Contract\r?\n<!-- gofer:always-on-eai:start -->[\s\S]*?<!-- gofer:always-on-eai:end -->/;
|
|
55
|
+
const MAX_DIAGNOSTIC_CHARACTERS = 4096;
|
|
56
|
+
const VS_CODE_INSTRUCTIONS_FRONTMATTER = `---
|
|
57
|
+
name: EAI Gofer
|
|
58
|
+
description: Apply the EAI Gofer delivery contract to every workspace request.
|
|
59
|
+
applyTo: '**'
|
|
60
|
+
---`;
|
|
61
|
+
const LEGACY_VS_CODE_INSTRUCTIONS_KEY = 'github.copilot.chat.codeGeneration.instructions';
|
|
62
|
+
|
|
63
|
+
function escapeRegExp(value) {
|
|
64
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function sanitizeDiagnostic(value, { home = os.homedir() } = {}) {
|
|
68
|
+
if (value === undefined || value === null) return '';
|
|
69
|
+
let output = String(value)
|
|
70
|
+
.replace(/\u001b\[[0-?]*[ -\/]*[@-~]/g, '')
|
|
71
|
+
.replace(/(https?:\/\/)[^\s/@:]+:[^\s/@]+@/gi, '$1<redacted>@')
|
|
72
|
+
.replace(/\b(Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>')
|
|
73
|
+
.replace(
|
|
74
|
+
/\b((?:api[-_ ]?key|access[-_ ]?token|refresh[-_ ]?token|token|secret|password|credential)\s*[:=]\s*)[^\s,;]+/gi,
|
|
75
|
+
'$1<redacted>'
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (home && path.isAbsolute(home)) {
|
|
79
|
+
output = output.replace(new RegExp(escapeRegExp(path.resolve(home)), 'gi'), '<home>');
|
|
80
|
+
}
|
|
81
|
+
output = output
|
|
82
|
+
.replace(/\b[A-Za-z]:[\\/]Users[\\/][^\\/\s"']+/gi, '<home>')
|
|
83
|
+
.replace(/\/(?:Users|home)\/[^/\s"']+/g, '<home>')
|
|
84
|
+
.replace(/\/(?:private\/)?tmp\/[^\s"']+/g, '<local-path>')
|
|
85
|
+
.replace(/\/var\/folders\/[^\s"']+/g, '<local-path>')
|
|
86
|
+
.replace(/(^|[\s"'[(=])[A-Za-z]:[\\/][^\s"'\]),;]+/gm, '$1<local-path>')
|
|
87
|
+
.replace(/(^|[\s"'[(=])\/(?!\/)[^\s"'\]),;]+/gm, '$1<local-path>')
|
|
88
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '');
|
|
89
|
+
|
|
90
|
+
if (output.length > MAX_DIAGNOSTIC_CHARACTERS) {
|
|
91
|
+
return `${output.slice(0, MAX_DIAGNOSTIC_CHARACTERS)}…<truncated>`;
|
|
92
|
+
}
|
|
93
|
+
return output;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function sanitizeLocalPath(value) {
|
|
97
|
+
if (!value) return undefined;
|
|
98
|
+
return `<local-path>/${path.basename(String(value))}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function sanitizeSurfaceUpdateOutput(value, options = {}, key = '') {
|
|
102
|
+
if (value === undefined || value === null) return value;
|
|
103
|
+
if (typeof value === 'string') {
|
|
104
|
+
if (/(?:path|root|cwd|directory)$/i.test(key) && isLocalMarketplacePath(value)) {
|
|
105
|
+
return sanitizeLocalPath(value);
|
|
106
|
+
}
|
|
107
|
+
return sanitizeDiagnostic(value, options);
|
|
108
|
+
}
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
return value.slice(0, 100).map((entry) => sanitizeSurfaceUpdateOutput(entry, options));
|
|
111
|
+
}
|
|
112
|
+
if (typeof value === 'object') {
|
|
113
|
+
return Object.fromEntries(
|
|
114
|
+
Object.entries(value)
|
|
115
|
+
.slice(0, 100)
|
|
116
|
+
.map(([entryKey, entryValue]) => [
|
|
117
|
+
entryKey,
|
|
118
|
+
/(?:password|secret|token|credential|api[-_]?key)/i.test(entryKey)
|
|
119
|
+
? '<redacted>'
|
|
120
|
+
: sanitizeSurfaceUpdateOutput(entryValue, options, entryKey),
|
|
121
|
+
])
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function command(command, args, label) {
|
|
128
|
+
return { command, args, label };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getAlwaysOnInstructionPath(host, {
|
|
132
|
+
home = os.homedir(),
|
|
133
|
+
platform = process.platform,
|
|
134
|
+
env = process.env,
|
|
135
|
+
} = {}) {
|
|
136
|
+
if (host === 'claude') return path.join(home, '.claude', 'CLAUDE.md');
|
|
137
|
+
if (host === 'codex') return path.join(home, '.codex', 'AGENTS.md');
|
|
138
|
+
if (host === 'copilot') return path.join(home, '.copilot', 'copilot-instructions.md');
|
|
139
|
+
if (host === 'antigravity') return path.join(home, '.gemini', 'GEMINI.md');
|
|
140
|
+
if (host === 'vscode') return path.join(home, '.copilot', 'instructions', 'eai-gofer.instructions.md');
|
|
141
|
+
throw new Error(`Unsupported host: ${host}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getLegacyVsCodeSettingsPath({ home, platform, env }) {
|
|
145
|
+
if (platform === 'darwin') {
|
|
146
|
+
return path.join(home, 'Library', 'Application Support', 'Code', 'User', 'settings.json');
|
|
147
|
+
}
|
|
148
|
+
if (platform === 'win32') {
|
|
149
|
+
return path.join(
|
|
150
|
+
env.APPDATA || path.join(home, 'AppData', 'Roaming'),
|
|
151
|
+
'Code',
|
|
152
|
+
'User',
|
|
153
|
+
'settings.json'
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return path.join(env.XDG_CONFIG_HOME || path.join(home, '.config'), 'Code', 'User', 'settings.json');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function getBundledAlwaysOnSkillPath(host, {
|
|
160
|
+
pluginRoot = DEFAULT_PLUGIN_ROOT,
|
|
161
|
+
} = {}) {
|
|
162
|
+
if (host !== 'grok') throw new Error(`Unsupported bundled-skill host: ${host}`);
|
|
163
|
+
return path.join(pluginRoot, 'skills', 'eai', 'SKILL.md');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function verifyAlwaysOnSkill(content, host) {
|
|
167
|
+
if (
|
|
168
|
+
!content.includes(ALWAYS_ON_EAI_START) ||
|
|
169
|
+
!content.includes(ALWAYS_ON_EAI_END) ||
|
|
170
|
+
!content.includes('Apply this contract to every request')
|
|
171
|
+
) {
|
|
172
|
+
throw new Error(`The bundled ${host} EAI skill does not contain the always-on contract.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function upsertAlwaysOnEaiSection(content) {
|
|
177
|
+
if (ALWAYS_ON_EAI_MARKER.test(content)) {
|
|
178
|
+
return content.replace(ALWAYS_ON_EAI_MARKER, ALWAYS_ON_EAI_SECTION);
|
|
179
|
+
}
|
|
180
|
+
const separator = content.length === 0 ? '' : content.endsWith('\n') ? '\n' : '\n\n';
|
|
181
|
+
return `${content}${separator}${ALWAYS_ON_EAI_SECTION}\n`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function upsertVsCodeInstructionFile(content) {
|
|
185
|
+
const withContract = upsertAlwaysOnEaiSection(content);
|
|
186
|
+
const frontmatter = withContract.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
187
|
+
if (!frontmatter) {
|
|
188
|
+
return `${VS_CODE_INSTRUCTIONS_FRONTMATTER}\n\n${withContract}`;
|
|
189
|
+
}
|
|
190
|
+
const body = frontmatter[1];
|
|
191
|
+
const updatedBody = /^applyTo\s*:/m.test(body)
|
|
192
|
+
? body.replace(/^applyTo\s*:.*$/m, "applyTo: '**'")
|
|
193
|
+
: `${body}\napplyTo: '**'`;
|
|
194
|
+
return `${withContract.slice(0, frontmatter.index)}---\n${updatedBody}\n---${withContract.slice((frontmatter.index ?? 0) + frontmatter[0].length)}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function skipJsoncWhitespaceAndComments(content, start) {
|
|
198
|
+
let index = start;
|
|
199
|
+
while (index < content.length) {
|
|
200
|
+
if (/\s/.test(content[index])) {
|
|
201
|
+
index += 1;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (content[index] === '/' && content[index + 1] === '/') {
|
|
205
|
+
index += 2;
|
|
206
|
+
while (index < content.length && !['\n', '\r'].includes(content[index])) index += 1;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (content[index] === '/' && content[index + 1] === '*') {
|
|
210
|
+
index += 2;
|
|
211
|
+
while (index < content.length && !(content[index] === '*' && content[index + 1] === '/')) {
|
|
212
|
+
index += 1;
|
|
213
|
+
}
|
|
214
|
+
index += 2;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
return index;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parseJsoncObject(content) {
|
|
223
|
+
const output = [];
|
|
224
|
+
let index = 0;
|
|
225
|
+
let quote = '';
|
|
226
|
+
let escaped = false;
|
|
227
|
+
while (index < content.length) {
|
|
228
|
+
const character = content[index];
|
|
229
|
+
const next = content[index + 1];
|
|
230
|
+
if (quote) {
|
|
231
|
+
output.push(character);
|
|
232
|
+
if (escaped) escaped = false;
|
|
233
|
+
else if (character === '\\') escaped = true;
|
|
234
|
+
else if (character === quote) quote = '';
|
|
235
|
+
index += 1;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (character === '"') {
|
|
239
|
+
quote = character;
|
|
240
|
+
output.push(character);
|
|
241
|
+
index += 1;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (character === '/' && next === '/') {
|
|
245
|
+
index += 2;
|
|
246
|
+
while (index < content.length && !['\n', '\r'].includes(content[index])) index += 1;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (character === '/' && next === '*') {
|
|
250
|
+
index += 2;
|
|
251
|
+
while (index < content.length && !(content[index] === '*' && content[index + 1] === '/')) {
|
|
252
|
+
if (['\n', '\r'].includes(content[index])) output.push(content[index]);
|
|
253
|
+
index += 1;
|
|
254
|
+
}
|
|
255
|
+
index += 2;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (character === ',') {
|
|
259
|
+
const lookahead = skipJsoncWhitespaceAndComments(content, index + 1);
|
|
260
|
+
if (content[lookahead] === '}' || content[lookahead] === ']') {
|
|
261
|
+
index += 1;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
output.push(character);
|
|
266
|
+
index += 1;
|
|
267
|
+
}
|
|
268
|
+
const parsed = JSON.parse(output.join(''));
|
|
269
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
|
|
270
|
+
throw new Error('VS Code settings.json must contain a JSON object.');
|
|
271
|
+
}
|
|
272
|
+
return parsed;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function findJsonStringEnd(content, start) {
|
|
276
|
+
let escaped = false;
|
|
277
|
+
for (let index = start + 1; index < content.length; index += 1) {
|
|
278
|
+
if (escaped) escaped = false;
|
|
279
|
+
else if (content[index] === '\\') escaped = true;
|
|
280
|
+
else if (content[index] === '"') return index + 1;
|
|
281
|
+
}
|
|
282
|
+
throw new Error('VS Code settings.json contains an unterminated string.');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function findJsoncValueEnd(content, start) {
|
|
286
|
+
const opening = content[start];
|
|
287
|
+
if (opening === '"') return findJsonStringEnd(content, start);
|
|
288
|
+
if (opening !== '[' && opening !== '{') {
|
|
289
|
+
let index = start;
|
|
290
|
+
while (index < content.length && !',}]'.includes(content[index])) index += 1;
|
|
291
|
+
return index;
|
|
292
|
+
}
|
|
293
|
+
const closing = opening === '[' ? ']' : '}';
|
|
294
|
+
let depth = 0;
|
|
295
|
+
let index = start;
|
|
296
|
+
while (index < content.length) {
|
|
297
|
+
if (content[index] === '"') {
|
|
298
|
+
index = findJsonStringEnd(content, index);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (content[index] === '/' && ['/', '*'].includes(content[index + 1])) {
|
|
302
|
+
index = skipJsoncWhitespaceAndComments(content, index);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (content[index] === opening) depth += 1;
|
|
306
|
+
if (content[index] === closing) {
|
|
307
|
+
depth -= 1;
|
|
308
|
+
if (depth === 0) return index + 1;
|
|
309
|
+
}
|
|
310
|
+
index += 1;
|
|
311
|
+
}
|
|
312
|
+
throw new Error('VS Code settings.json contains an unterminated value.');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function findJsoncPropertyRange(content, key) {
|
|
316
|
+
let depth = 0;
|
|
317
|
+
for (let index = 0; index < content.length;) {
|
|
318
|
+
if (content[index] === '"') {
|
|
319
|
+
const stringStart = index;
|
|
320
|
+
const stringEnd = findJsonStringEnd(content, index);
|
|
321
|
+
const next = skipJsoncWhitespaceAndComments(content, stringEnd);
|
|
322
|
+
if (depth === 1 && content[next] === ':' && JSON.parse(content.slice(stringStart, stringEnd)) === key) {
|
|
323
|
+
const valueStart = skipJsoncWhitespaceAndComments(content, next + 1);
|
|
324
|
+
const valueEnd = findJsoncValueEnd(content, valueStart);
|
|
325
|
+
const afterValue = skipJsoncWhitespaceAndComments(content, valueEnd);
|
|
326
|
+
if (content[afterValue] === ',') {
|
|
327
|
+
return { removeStart: stringStart, removeEnd: afterValue + 1, valueStart, valueEnd };
|
|
328
|
+
}
|
|
329
|
+
let beforeKey = stringStart - 1;
|
|
330
|
+
while (beforeKey >= 0 && /\s/.test(content[beforeKey])) beforeKey -= 1;
|
|
331
|
+
if (content[beforeKey] === ',') {
|
|
332
|
+
return { removeStart: beforeKey, removeEnd: valueEnd, valueStart, valueEnd };
|
|
333
|
+
}
|
|
334
|
+
return { removeStart: stringStart, removeEnd: valueEnd, valueStart, valueEnd };
|
|
335
|
+
}
|
|
336
|
+
index = stringEnd;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (content[index] === '/' && ['/', '*'].includes(content[index + 1])) {
|
|
340
|
+
index = skipJsoncWhitespaceAndComments(content, index);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (content[index] === '{' || content[index] === '[') depth += 1;
|
|
344
|
+
if (content[index] === '}' || content[index] === ']') depth -= 1;
|
|
345
|
+
index += 1;
|
|
346
|
+
}
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function removeLegacyVsCodeGoferInstruction(content) {
|
|
351
|
+
const settings = parseJsoncObject(content);
|
|
352
|
+
const instructions = settings[LEGACY_VS_CODE_INSTRUCTIONS_KEY];
|
|
353
|
+
if (!Array.isArray(instructions)) return content;
|
|
354
|
+
const retained = instructions.filter(
|
|
355
|
+
(entry) => typeof entry?.text !== 'string' || !entry.text.includes(ALWAYS_ON_EAI_START)
|
|
356
|
+
);
|
|
357
|
+
if (retained.length === instructions.length) return content;
|
|
358
|
+
const range = findJsoncPropertyRange(content, LEGACY_VS_CODE_INSTRUCTIONS_KEY);
|
|
359
|
+
if (!range) throw new Error('Could not safely locate the legacy VS Code Gofer setting.');
|
|
360
|
+
if (retained.length === 0) {
|
|
361
|
+
return `${content.slice(0, range.removeStart)}${content.slice(range.removeEnd)}`;
|
|
362
|
+
}
|
|
363
|
+
return `${content.slice(0, range.valueStart)}${JSON.stringify(retained, null, 2)}${content.slice(range.valueEnd)}`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function getInstructionSecurityRoot(host, { home, platform, env }) {
|
|
367
|
+
return home;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function assertNoSymlinkComponents(targetPath, securityRoot, fileSystem) {
|
|
371
|
+
if (!path.isAbsolute(targetPath) || !path.isAbsolute(securityRoot)) {
|
|
372
|
+
throw new Error('Refusing to update a non-absolute user instruction target.');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const relativeTarget = path.relative(securityRoot, targetPath);
|
|
376
|
+
if (
|
|
377
|
+
relativeTarget === '..' ||
|
|
378
|
+
relativeTarget.startsWith(`..${path.sep}`) ||
|
|
379
|
+
path.isAbsolute(relativeTarget)
|
|
380
|
+
) {
|
|
381
|
+
throw new Error('Refusing to update a user instruction target outside its configuration root.');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const components = relativeTarget.split(path.sep).filter(Boolean);
|
|
385
|
+
let currentPath = securityRoot;
|
|
386
|
+
try {
|
|
387
|
+
const rootStatus = await fileSystem.lstat(currentPath);
|
|
388
|
+
if (rootStatus.isSymbolicLink()) {
|
|
389
|
+
throw new Error('Refusing to update a user instruction target through a symbolic link.');
|
|
390
|
+
}
|
|
391
|
+
} catch (error) {
|
|
392
|
+
if (error?.code === 'ENOENT') return;
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
for (const component of components) {
|
|
396
|
+
currentPath = path.join(currentPath, component);
|
|
397
|
+
try {
|
|
398
|
+
const status = await fileSystem.lstat(currentPath);
|
|
399
|
+
if (status.isSymbolicLink()) {
|
|
400
|
+
throw new Error('Refusing to update a user instruction target through a symbolic link.');
|
|
401
|
+
}
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (error?.code === 'ENOENT') return;
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function readText(targetPath, securityRoot, fileSystem) {
|
|
410
|
+
await assertNoSymlinkComponents(targetPath, securityRoot, fileSystem);
|
|
411
|
+
let handle;
|
|
412
|
+
try {
|
|
413
|
+
const status = await fileSystem.lstat(targetPath);
|
|
414
|
+
if (!status.isFile()) {
|
|
415
|
+
throw new Error('Refusing to update a user instruction target that is not a regular file.');
|
|
416
|
+
}
|
|
417
|
+
handle = await fileSystem.open(
|
|
418
|
+
targetPath,
|
|
419
|
+
fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0)
|
|
420
|
+
);
|
|
421
|
+
const openedStatus = await handle.stat();
|
|
422
|
+
if (!openedStatus.isFile() || !sameFileIdentity(
|
|
423
|
+
{ exists: true, dev: status.dev, ino: status.ino },
|
|
424
|
+
{ exists: true, dev: openedStatus.dev, ino: openedStatus.ino }
|
|
425
|
+
)) {
|
|
426
|
+
throw new Error('Refusing to read a user instruction target that changed during the update.');
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
content: await handle.readFile('utf8'),
|
|
430
|
+
exists: true,
|
|
431
|
+
mode: openedStatus.mode & 0o777,
|
|
432
|
+
dev: openedStatus.dev,
|
|
433
|
+
ino: openedStatus.ino,
|
|
434
|
+
};
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (error?.code === 'ENOENT') {
|
|
437
|
+
return { content: '', exists: false, mode: 0o600, dev: undefined, ino: undefined };
|
|
438
|
+
}
|
|
439
|
+
throw error;
|
|
440
|
+
} finally {
|
|
441
|
+
await handle?.close();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function sameFileIdentity(left, right) {
|
|
446
|
+
if (!left.exists || !right.exists) return left.exists === right.exists;
|
|
447
|
+
if (left.dev === undefined || left.ino === undefined || right.dev === undefined || right.ino === undefined) {
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function writeOpenFileContent(handle, content) {
|
|
454
|
+
const bytes = Buffer.from(content, 'utf8');
|
|
455
|
+
let offset = 0;
|
|
456
|
+
while (offset < bytes.length) {
|
|
457
|
+
const { bytesWritten } = await handle.write(bytes, offset, bytes.length - offset, offset);
|
|
458
|
+
if (bytesWritten === 0) throw new Error('Unable to make progress while writing instructions.');
|
|
459
|
+
offset += bytesWritten;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function readPathIdentity(targetPath, fileSystem) {
|
|
464
|
+
try {
|
|
465
|
+
const status = await fileSystem.lstat(targetPath);
|
|
466
|
+
return {
|
|
467
|
+
exists: true,
|
|
468
|
+
file: status.isFile(),
|
|
469
|
+
symlink: status.isSymbolicLink(),
|
|
470
|
+
dev: status.dev,
|
|
471
|
+
ino: status.ino,
|
|
472
|
+
};
|
|
473
|
+
} catch (error) {
|
|
474
|
+
if (error?.code === 'ENOENT') {
|
|
475
|
+
return { exists: false, file: false, symlink: false, dev: undefined, ino: undefined };
|
|
476
|
+
}
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function unlinkOnlyIfIdentityMatches(candidatePath, expected, fileSystem) {
|
|
482
|
+
try {
|
|
483
|
+
const current = await readPathIdentity(candidatePath, fileSystem);
|
|
484
|
+
if (current.exists && sameFileIdentity(expected, current)) {
|
|
485
|
+
await fileSystem.unlink(candidatePath);
|
|
486
|
+
}
|
|
487
|
+
} catch {
|
|
488
|
+
// Preserve the primary fail-closed error. Never unlink an unverified path.
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function writeText(targetPath, content, securityRoot, fileSystem, original, platform) {
|
|
493
|
+
const parentPath = path.dirname(targetPath);
|
|
494
|
+
await fileSystem.mkdir(parentPath, { recursive: true });
|
|
495
|
+
await assertNoSymlinkComponents(targetPath, securityRoot, fileSystem);
|
|
496
|
+
const parentBeforeOpen = await fileSystem.lstat(parentPath);
|
|
497
|
+
if (parentBeforeOpen.isSymbolicLink() || !parentBeforeOpen.isDirectory()) {
|
|
498
|
+
throw new Error('Refusing to update instructions through an unsafe parent directory.');
|
|
499
|
+
}
|
|
500
|
+
const parentHandle = await fileSystem.open(
|
|
501
|
+
parentPath,
|
|
502
|
+
fsConstants.O_RDONLY | (fsConstants.O_DIRECTORY ?? 0)
|
|
503
|
+
);
|
|
504
|
+
const parentStatus = await parentHandle.stat();
|
|
505
|
+
if (!parentStatus.isDirectory()) {
|
|
506
|
+
await parentHandle.close();
|
|
507
|
+
throw new Error('Refusing to update instructions through a non-directory parent.');
|
|
508
|
+
}
|
|
509
|
+
if (!sameFileIdentity(
|
|
510
|
+
{ exists: true, dev: parentBeforeOpen.dev, ino: parentBeforeOpen.ino },
|
|
511
|
+
{ exists: true, dev: parentStatus.dev, ino: parentStatus.ino }
|
|
512
|
+
)) {
|
|
513
|
+
await parentHandle.close();
|
|
514
|
+
throw new Error('Refusing to update instructions through a parent that changed while opening.');
|
|
515
|
+
}
|
|
516
|
+
let temporaryHandle;
|
|
517
|
+
let temporaryPath;
|
|
518
|
+
let temporaryIdentity;
|
|
519
|
+
let renamed = false;
|
|
520
|
+
try {
|
|
521
|
+
const beforeCreateParent = await fileSystem.lstat(parentPath);
|
|
522
|
+
if (!sameFileIdentity(
|
|
523
|
+
{ exists: true, dev: parentStatus.dev, ino: parentStatus.ino },
|
|
524
|
+
{ exists: true, dev: beforeCreateParent.dev, ino: beforeCreateParent.ino }
|
|
525
|
+
)) {
|
|
526
|
+
throw new Error('Refusing to replace instructions through a parent that changed during the update.');
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const temporaryName = `.${path.basename(targetPath)}.gofer-${randomBytes(16).toString('hex')}.tmp`;
|
|
530
|
+
temporaryPath = path.join(parentPath, temporaryName);
|
|
531
|
+
temporaryHandle = await fileSystem.open(
|
|
532
|
+
temporaryPath,
|
|
533
|
+
fsConstants.O_WRONLY |
|
|
534
|
+
fsConstants.O_CREAT |
|
|
535
|
+
fsConstants.O_EXCL |
|
|
536
|
+
(fsConstants.O_NOFOLLOW ?? 0),
|
|
537
|
+
0o600
|
|
538
|
+
);
|
|
539
|
+
const temporaryStatus = await temporaryHandle.stat();
|
|
540
|
+
if (!temporaryStatus.isFile()) {
|
|
541
|
+
throw new Error('Refusing to create a non-file instruction replacement.');
|
|
542
|
+
}
|
|
543
|
+
temporaryIdentity = {
|
|
544
|
+
exists: true,
|
|
545
|
+
dev: temporaryStatus.dev,
|
|
546
|
+
ino: temporaryStatus.ino,
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const afterCreateParent = await fileSystem.lstat(parentPath);
|
|
550
|
+
if (!sameFileIdentity(
|
|
551
|
+
{ exists: true, dev: parentStatus.dev, ino: parentStatus.ino },
|
|
552
|
+
{ exists: true, dev: afterCreateParent.dev, ino: afterCreateParent.ino }
|
|
553
|
+
)) {
|
|
554
|
+
throw new Error('Refusing to replace instructions through a parent that changed during the update.');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
await writeOpenFileContent(temporaryHandle, content);
|
|
558
|
+
await temporaryHandle.chmod(original.mode);
|
|
559
|
+
await temporaryHandle.sync();
|
|
560
|
+
await temporaryHandle.close();
|
|
561
|
+
temporaryHandle = undefined;
|
|
562
|
+
|
|
563
|
+
const currentTarget = await readPathIdentity(targetPath, fileSystem);
|
|
564
|
+
if (
|
|
565
|
+
currentTarget.symlink ||
|
|
566
|
+
(currentTarget.exists && !currentTarget.file) ||
|
|
567
|
+
!sameFileIdentity(original, currentTarget)
|
|
568
|
+
) {
|
|
569
|
+
throw new Error('Refusing to replace a user instruction target that changed during the update.');
|
|
570
|
+
}
|
|
571
|
+
const beforeRenameParent = await fileSystem.lstat(parentPath);
|
|
572
|
+
if (!sameFileIdentity(
|
|
573
|
+
{ exists: true, dev: parentStatus.dev, ino: parentStatus.ino },
|
|
574
|
+
{ exists: true, dev: beforeRenameParent.dev, ino: beforeRenameParent.ino }
|
|
575
|
+
)) {
|
|
576
|
+
throw new Error('Refusing to replace instructions through a parent that changed during the update.');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
await fileSystem.rename(temporaryPath, targetPath);
|
|
580
|
+
renamed = true;
|
|
581
|
+
|
|
582
|
+
const [afterRenameParent, installedTarget] = await Promise.all([
|
|
583
|
+
fileSystem.lstat(parentPath),
|
|
584
|
+
readPathIdentity(targetPath, fileSystem),
|
|
585
|
+
]);
|
|
586
|
+
if (
|
|
587
|
+
!sameFileIdentity(
|
|
588
|
+
{ exists: true, dev: parentStatus.dev, ino: parentStatus.ino },
|
|
589
|
+
{ exists: true, dev: afterRenameParent.dev, ino: afterRenameParent.ino }
|
|
590
|
+
) ||
|
|
591
|
+
!sameFileIdentity(temporaryIdentity, installedTarget)
|
|
592
|
+
) {
|
|
593
|
+
await unlinkOnlyIfIdentityMatches(targetPath, temporaryIdentity, fileSystem);
|
|
594
|
+
throw new Error('Refusing to keep instructions after the parent changed during the update.');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (platform !== 'win32') {
|
|
598
|
+
await parentHandle.sync();
|
|
599
|
+
}
|
|
600
|
+
} catch (error) {
|
|
601
|
+
if (!renamed && temporaryPath && temporaryIdentity) {
|
|
602
|
+
await unlinkOnlyIfIdentityMatches(temporaryPath, temporaryIdentity, fileSystem);
|
|
603
|
+
}
|
|
604
|
+
throw error;
|
|
605
|
+
} finally {
|
|
606
|
+
await temporaryHandle?.close();
|
|
607
|
+
await parentHandle.close();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export async function configureAlwaysOnInstructions(hosts, {
|
|
612
|
+
home = os.homedir(),
|
|
613
|
+
platform = process.platform,
|
|
614
|
+
env = process.env,
|
|
615
|
+
fileSystem = fs,
|
|
616
|
+
pluginRoot = DEFAULT_PLUGIN_ROOT,
|
|
617
|
+
} = {}) {
|
|
618
|
+
const results = [];
|
|
619
|
+
for (const host of [...new Set(hosts)]) {
|
|
620
|
+
let targetPath;
|
|
621
|
+
try {
|
|
622
|
+
if (host === 'grok') {
|
|
623
|
+
targetPath = getBundledAlwaysOnSkillPath(host, { pluginRoot });
|
|
624
|
+
const content = await fileSystem.readFile(targetPath, 'utf8');
|
|
625
|
+
verifyAlwaysOnSkill(content, host);
|
|
626
|
+
results.push({
|
|
627
|
+
host,
|
|
628
|
+
targetPath: sanitizeLocalPath(targetPath),
|
|
629
|
+
ok: true,
|
|
630
|
+
managedByPlugin: true,
|
|
631
|
+
});
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
targetPath = getAlwaysOnInstructionPath(host, { home, platform, env });
|
|
636
|
+
const securityRoot = getInstructionSecurityRoot(host, { home, platform, env });
|
|
637
|
+
const original = await readText(targetPath, securityRoot, fileSystem);
|
|
638
|
+
const existing = original.content;
|
|
639
|
+
if (host === 'vscode') {
|
|
640
|
+
const updated = upsertVsCodeInstructionFile(existing);
|
|
641
|
+
if (updated !== existing) {
|
|
642
|
+
await writeText(targetPath, updated, securityRoot, fileSystem, original, platform);
|
|
643
|
+
}
|
|
644
|
+
const legacySettingsPath = getLegacyVsCodeSettingsPath({ home, platform, env });
|
|
645
|
+
const legacyOriginal = await readText(legacySettingsPath, securityRoot, fileSystem);
|
|
646
|
+
if (legacyOriginal.exists) {
|
|
647
|
+
const migratedSettings = removeLegacyVsCodeGoferInstruction(legacyOriginal.content);
|
|
648
|
+
if (migratedSettings !== legacyOriginal.content) {
|
|
649
|
+
await writeText(
|
|
650
|
+
legacySettingsPath,
|
|
651
|
+
migratedSettings,
|
|
652
|
+
securityRoot,
|
|
653
|
+
fileSystem,
|
|
654
|
+
legacyOriginal,
|
|
655
|
+
platform
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
const updated = upsertAlwaysOnEaiSection(existing);
|
|
661
|
+
if (updated !== existing) {
|
|
662
|
+
await writeText(targetPath, updated, securityRoot, fileSystem, original, platform);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
results.push({ host, targetPath: sanitizeLocalPath(targetPath), ok: true });
|
|
666
|
+
} catch (error) {
|
|
667
|
+
results.push({
|
|
668
|
+
host,
|
|
669
|
+
targetPath: sanitizeLocalPath(targetPath),
|
|
670
|
+
ok: false,
|
|
671
|
+
managedByPlugin: host === 'grok',
|
|
672
|
+
error: sanitizeDiagnostic(error instanceof Error ? error.message : String(error), { home }),
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return results;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const SURFACE_ACTIONS = {
|
|
680
|
+
claude: {
|
|
681
|
+
install: [
|
|
682
|
+
command('claude', ['plugin', 'marketplace', 'add', REPOSITORY_URL, '--scope', 'user', '--sparse', '.claude-plugin', '--sparse', 'plugins/eai-gofer'], 'Add the EAI Gofer marketplace'),
|
|
683
|
+
command('claude', ['plugin', 'install', 'eai-gofer@eai-gofer', '--scope', 'user'], 'Install EAI Gofer'),
|
|
684
|
+
],
|
|
685
|
+
update: [
|
|
686
|
+
command('claude', ['plugin', 'marketplace', 'update', 'eai-gofer'], 'Refresh the EAI Gofer marketplace'),
|
|
687
|
+
command('claude', ['plugin', 'update', 'eai-gofer@eai-gofer', '--scope', 'user'], 'Update EAI Gofer'),
|
|
688
|
+
],
|
|
689
|
+
refresh: 'Run /reload-plugins, then start a new Claude Code conversation.',
|
|
690
|
+
},
|
|
691
|
+
codex: {
|
|
692
|
+
install: [
|
|
693
|
+
command('codex', ['plugin', 'marketplace', 'add', REPOSITORY_URL, '--sparse', '.agents/plugins', '--sparse', 'plugins/eai-gofer'], 'Add the EAI Gofer marketplace'),
|
|
694
|
+
command('codex', ['plugin', 'add', 'eai-gofer@eai-gofer'], 'Install EAI Gofer'),
|
|
695
|
+
],
|
|
696
|
+
update: [
|
|
697
|
+
command('codex', ['plugin', 'marketplace', 'upgrade', 'eai-gofer'], 'Refresh the EAI Gofer marketplace'),
|
|
698
|
+
command('codex', ['plugin', 'add', 'eai-gofer@eai-gofer'], 'Apply the refreshed EAI Gofer plugin'),
|
|
699
|
+
],
|
|
700
|
+
refresh: 'Start a new Codex task or restart Codex so it loads the refreshed plugin.',
|
|
701
|
+
},
|
|
702
|
+
copilot: {
|
|
703
|
+
install: [
|
|
704
|
+
command('copilot', ['plugin', 'marketplace', 'add', REPOSITORY_URL], 'Add the EAI Gofer marketplace'),
|
|
705
|
+
command('copilot', ['plugin', 'install', 'eai-gofer@eai-gofer'], 'Install EAI Gofer'),
|
|
706
|
+
],
|
|
707
|
+
update: [
|
|
708
|
+
command('copilot', ['plugin', 'marketplace', 'update', 'eai-gofer'], 'Refresh the EAI Gofer marketplace'),
|
|
709
|
+
command('copilot', ['plugin', 'update', 'eai-gofer@eai-gofer'], 'Update EAI Gofer'),
|
|
710
|
+
],
|
|
711
|
+
refresh: 'Run /restart in Copilot CLI or start a new Copilot app chat.',
|
|
712
|
+
},
|
|
713
|
+
antigravity: {
|
|
714
|
+
install: [
|
|
715
|
+
command('agy', ['plugin', 'install', REPOSITORY_URL], 'Install EAI Gofer'),
|
|
716
|
+
],
|
|
717
|
+
update: [
|
|
718
|
+
command('agy', ['plugin', 'install', REPOSITORY_URL], 'Reinstall the current EAI Gofer plugin'),
|
|
719
|
+
],
|
|
720
|
+
refresh: 'Start a new Antigravity session so it loads the installed plugin.',
|
|
721
|
+
},
|
|
722
|
+
grok: {
|
|
723
|
+
install: [
|
|
724
|
+
command('grok', ['plugin', 'install', '--trust', REPOSITORY_URL], 'Install EAI Gofer'),
|
|
725
|
+
],
|
|
726
|
+
update: [
|
|
727
|
+
command('grok', ['plugin', 'update', 'eai-gofer'], 'Update EAI Gofer'),
|
|
728
|
+
],
|
|
729
|
+
refresh: 'Start a new Grok Build session so it loads the updated plugin.',
|
|
730
|
+
},
|
|
731
|
+
vscode: {
|
|
732
|
+
install: [
|
|
733
|
+
command('code', ['--install-extension', VS_CODE_EXTENSION_ID, '--force'], 'Install or update the EAI Gofer VS Code extension'),
|
|
734
|
+
],
|
|
735
|
+
update: [
|
|
736
|
+
command('code', ['--install-extension', VS_CODE_EXTENSION_ID, '--force'], 'Install or update the EAI Gofer VS Code extension'),
|
|
737
|
+
],
|
|
738
|
+
refresh: 'Run Developer: Reload Window in VS Code.',
|
|
739
|
+
},
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
export function parseArgs(argv) {
|
|
743
|
+
const result = { action: 'inspect', host: 'auto', execute: false, json: false, help: false };
|
|
744
|
+
if (argv.includes('--help') || argv.includes('-h')) return { ...result, help: true };
|
|
745
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
746
|
+
const value = argv[index];
|
|
747
|
+
const nextValue = argv[index + 1];
|
|
748
|
+
if (value === '--action') {
|
|
749
|
+
if (!nextValue || nextValue.startsWith('-')) throw new Error('Missing value for --action.');
|
|
750
|
+
result.action = argv[++index];
|
|
751
|
+
} else if (value === '--host') {
|
|
752
|
+
if (!nextValue || nextValue.startsWith('-')) throw new Error('Missing value for --host.');
|
|
753
|
+
result.host = argv[++index];
|
|
754
|
+
}
|
|
755
|
+
else if (value === '--execute') result.execute = true;
|
|
756
|
+
else if (value === '--json') result.json = true;
|
|
757
|
+
else if (value === '--help' || value === '-h') result.help = true;
|
|
758
|
+
else throw new Error('Unsupported option. Use --action, --host, --execute, --json, or --help.');
|
|
759
|
+
}
|
|
760
|
+
if (!['inspect', 'install', 'update'].includes(result.action)) {
|
|
761
|
+
throw new Error(`Unsupported action: ${result.action}. Use inspect, install, or update.`);
|
|
762
|
+
}
|
|
763
|
+
result.host = LEGACY_HOST_ALIASES[result.host] || result.host;
|
|
764
|
+
if (!['auto', 'all', ...SUPPORTED_HOSTS].includes(result.host)) {
|
|
765
|
+
throw new Error(`Unsupported host: ${result.host}. Use auto, all, ${SUPPORTED_HOSTS.join(', ')}`);
|
|
766
|
+
}
|
|
767
|
+
return result;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export function resolveHosts(host, currentHost = process.env.GOFER_HOST) {
|
|
771
|
+
const normalizedHost = LEGACY_HOST_ALIASES[host] || host;
|
|
772
|
+
const normalizedCurrentHost = LEGACY_HOST_ALIASES[currentHost] || currentHost;
|
|
773
|
+
if (normalizedHost === 'all') return [...SUPPORTED_HOSTS];
|
|
774
|
+
if (normalizedHost !== 'auto') {
|
|
775
|
+
if (!SUPPORTED_HOSTS.includes(normalizedHost)) {
|
|
776
|
+
throw new Error(`Unsupported host: ${normalizedHost}. Use auto, all, ${SUPPORTED_HOSTS.join(', ')}`);
|
|
777
|
+
}
|
|
778
|
+
return [normalizedHost];
|
|
779
|
+
}
|
|
780
|
+
if (SUPPORTED_HOSTS.includes(normalizedCurrentHost)) return [normalizedCurrentHost];
|
|
781
|
+
return [];
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
export function buildSurfacePlan({ action, host, currentHost }) {
|
|
785
|
+
if (!['inspect', 'install', 'update'].includes(action)) {
|
|
786
|
+
throw new Error(`Unsupported action: ${action}. Use inspect, install, or update.`);
|
|
787
|
+
}
|
|
788
|
+
const selectedHosts = resolveHosts(host, currentHost);
|
|
789
|
+
if (selectedHosts.length === 0) {
|
|
790
|
+
throw new Error(`Use --host with ${SUPPORTED_HOSTS.join(', ')}, or all.`);
|
|
791
|
+
}
|
|
792
|
+
return selectedHosts.map((surface) => ({
|
|
793
|
+
host: surface,
|
|
794
|
+
action,
|
|
795
|
+
commands: action === 'inspect' ? [] : SURFACE_ACTIONS[surface][action],
|
|
796
|
+
refresh: SURFACE_ACTIONS[surface].refresh,
|
|
797
|
+
}));
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
export async function inspectHost(host, execute = execFileAsync) {
|
|
801
|
+
const normalizedHost = LEGACY_HOST_ALIASES[host] || host;
|
|
802
|
+
if (!SUPPORTED_HOSTS.includes(normalizedHost)) {
|
|
803
|
+
throw new Error(`Unsupported host: ${normalizedHost}. Use ${SUPPORTED_HOSTS.join(', ')}`);
|
|
804
|
+
}
|
|
805
|
+
const executable = executableForHost(normalizedHost);
|
|
806
|
+
const listArgs = {
|
|
807
|
+
claude: ['plugin', 'list'],
|
|
808
|
+
codex: ['plugin', 'list', '--json'],
|
|
809
|
+
copilot: ['plugin', 'list'],
|
|
810
|
+
antigravity: ['plugin', 'list'],
|
|
811
|
+
grok: ['plugin', 'list'],
|
|
812
|
+
vscode: ['--list-extensions', '--show-versions'],
|
|
813
|
+
}[normalizedHost];
|
|
814
|
+
try {
|
|
815
|
+
const version = await execute(executable, ['--version'], { windowsHide: true });
|
|
816
|
+
const listing = await execute(executable, listArgs, { windowsHide: true });
|
|
817
|
+
return {
|
|
818
|
+
host: normalizedHost,
|
|
819
|
+
available: true,
|
|
820
|
+
version: sanitizeDiagnostic(version.stdout.trim().split('\n')[0]),
|
|
821
|
+
installed: pluginListingHasGofer(normalizedHost, listing.stdout),
|
|
822
|
+
};
|
|
823
|
+
} catch (error) {
|
|
824
|
+
return {
|
|
825
|
+
host: normalizedHost,
|
|
826
|
+
available: false,
|
|
827
|
+
installed: false,
|
|
828
|
+
error: sanitizeDiagnostic(error?.code ?? error?.message ?? String(error)),
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function isExactGoferIdentifier(host, value) {
|
|
834
|
+
const candidate = String(value).trim().replace(/[,:;]$/, '');
|
|
835
|
+
const version = String.raw`v?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?`;
|
|
836
|
+
if (host === 'vscode') {
|
|
837
|
+
return new RegExp(`^EnterpriseAI\\.gofer(?:@${version})?$`, 'i').test(candidate);
|
|
838
|
+
}
|
|
839
|
+
return new RegExp(`^eai-gofer(?:@(?:eai-gofer|${version}))?$`, 'i').test(candidate);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function structuredListingContainsGofer(host, value) {
|
|
843
|
+
if (typeof value === 'string') return isExactGoferIdentifier(host, value);
|
|
844
|
+
if (Array.isArray(value)) {
|
|
845
|
+
return value.some((entry) => structuredListingContainsGofer(host, entry));
|
|
846
|
+
}
|
|
847
|
+
if (value && typeof value === 'object') {
|
|
848
|
+
return Object.values(value).some((entry) => structuredListingContainsGofer(host, entry));
|
|
849
|
+
}
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
export function pluginListingHasGofer(host, output) {
|
|
854
|
+
const normalizedHost = LEGACY_HOST_ALIASES[host] || host;
|
|
855
|
+
if (!SUPPORTED_HOSTS.includes(normalizedHost)) return false;
|
|
856
|
+
|
|
857
|
+
if (normalizedHost === 'codex') {
|
|
858
|
+
try {
|
|
859
|
+
return structuredListingContainsGofer(normalizedHost, JSON.parse(String(output)));
|
|
860
|
+
} catch {
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
return String(output)
|
|
866
|
+
.split(/\r?\n/)
|
|
867
|
+
.map((line) => line.trim().replace(/^[*+\-✓✔•]+\s*/, ''))
|
|
868
|
+
.filter(Boolean)
|
|
869
|
+
.some((line) => {
|
|
870
|
+
const candidate = normalizedHost === 'vscode' ? line : line.split(/\s+/, 1)[0];
|
|
871
|
+
return isExactGoferIdentifier(normalizedHost, candidate);
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
export async function inspectHosts(hosts, inspect = inspectHost) {
|
|
876
|
+
return Promise.all(hosts.map((host) => inspect(host)));
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function executableForHost(host) {
|
|
880
|
+
if (host === 'vscode') return 'code';
|
|
881
|
+
if (host === 'antigravity') return 'agy';
|
|
882
|
+
return host;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function isLocalMarketplacePath(value) {
|
|
886
|
+
return /^(?:[A-Za-z]:[\\/]|[/~])/.test(value.trim());
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function isOfficialRepositoryUrl(value) {
|
|
890
|
+
const normalized = value.trim().replace(/\.git$/, '').replace(/\/$/, '');
|
|
891
|
+
return [
|
|
892
|
+
'https://github.com/eai-support/eai-gofer',
|
|
893
|
+
'git@github.com:eai-support/eai-gofer',
|
|
894
|
+
'ssh://git@github.com/eai-support/eai-gofer',
|
|
895
|
+
].includes(normalized);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
export async function inspectLocalCodexMarketplace(root, execute = execFileAsync) {
|
|
899
|
+
try {
|
|
900
|
+
const [status, remote, branch] = await Promise.all([
|
|
901
|
+
execute('git', ['-C', root, 'status', '--porcelain'], { windowsHide: true }),
|
|
902
|
+
execute('git', ['-C', root, 'remote', 'get-url', 'origin'], { windowsHide: true }),
|
|
903
|
+
execute('git', ['-C', root, 'branch', '--show-current'], { windowsHide: true }),
|
|
904
|
+
]);
|
|
905
|
+
return {
|
|
906
|
+
root,
|
|
907
|
+
clean: status.stdout.trim().length === 0,
|
|
908
|
+
official: isOfficialRepositoryUrl(remote.stdout),
|
|
909
|
+
branch: sanitizeDiagnostic(branch.stdout.trim()),
|
|
910
|
+
};
|
|
911
|
+
} catch (error) {
|
|
912
|
+
return {
|
|
913
|
+
root,
|
|
914
|
+
clean: false,
|
|
915
|
+
official: false,
|
|
916
|
+
branch: '',
|
|
917
|
+
error: sanitizeDiagnostic(error?.stderr?.trim() ?? error?.message ?? String(error)),
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
export async function inspectCodexMarketplace(execute = execFileAsync) {
|
|
923
|
+
try {
|
|
924
|
+
const result = await execute('codex', ['plugin', 'marketplace', 'list'], {
|
|
925
|
+
windowsHide: true,
|
|
926
|
+
});
|
|
927
|
+
const marketplace = result.stdout
|
|
928
|
+
.split(/\r?\n/)
|
|
929
|
+
.map((line) => line.trim())
|
|
930
|
+
.find((line) => line.startsWith('eai-gofer'));
|
|
931
|
+
const root = marketplace?.replace(/^eai-gofer\s+/, '').trim();
|
|
932
|
+
|
|
933
|
+
if (!root) {
|
|
934
|
+
return { type: 'unknown' };
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
return {
|
|
938
|
+
type: isLocalMarketplacePath(root) ? 'local' : 'git',
|
|
939
|
+
root,
|
|
940
|
+
};
|
|
941
|
+
} catch {
|
|
942
|
+
return { type: 'unknown' };
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
export async function runPlan(
|
|
947
|
+
plan,
|
|
948
|
+
{
|
|
949
|
+
inspect = inspectHost,
|
|
950
|
+
inspectMarketplace = inspectCodexMarketplace,
|
|
951
|
+
inspectLocalMarketplace = inspectLocalCodexMarketplace,
|
|
952
|
+
execute = execFileAsync,
|
|
953
|
+
cleanup = cleanupLocalSettings,
|
|
954
|
+
configureInstructions = configureAlwaysOnInstructions,
|
|
955
|
+
} = {}
|
|
956
|
+
) {
|
|
957
|
+
if (!Array.isArray(plan)) {
|
|
958
|
+
throw new Error('Surface plan must be an array.');
|
|
959
|
+
}
|
|
960
|
+
for (const surface of plan) {
|
|
961
|
+
if (!surface || !SUPPORTED_HOSTS.includes(surface.host)) {
|
|
962
|
+
throw new Error(`Unsupported host: ${surface?.host}. Use ${SUPPORTED_HOSTS.join(', ')}`);
|
|
963
|
+
}
|
|
964
|
+
if (!['install', 'update'].includes(surface.action)) {
|
|
965
|
+
throw new Error(`Unsupported executable action: ${surface.action}. Use install or update.`);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const executablePlan = plan.map((surface) => {
|
|
970
|
+
const canonicalCommands = SURFACE_ACTIONS[surface.host][surface.action];
|
|
971
|
+
if (JSON.stringify(surface.commands) !== JSON.stringify(canonicalCommands)) {
|
|
972
|
+
throw new Error(
|
|
973
|
+
`Surface plan for ${surface.host} must exactly match the packaged ${surface.action} action.`
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
return {
|
|
977
|
+
...surface,
|
|
978
|
+
commands: canonicalCommands.map((step) => ({ ...step, args: [...step.args] })),
|
|
979
|
+
};
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
const results = [];
|
|
983
|
+
let completedUpdate = false;
|
|
984
|
+
let planFailed = false;
|
|
985
|
+
const configuredHosts = [];
|
|
986
|
+
|
|
987
|
+
for (const surface of executablePlan) {
|
|
988
|
+
const availability = await inspect(surface.host);
|
|
989
|
+
if (!availability.available) {
|
|
990
|
+
planFailed = true;
|
|
991
|
+
results.push({
|
|
992
|
+
host: surface.host,
|
|
993
|
+
skipped: true,
|
|
994
|
+
ok: false,
|
|
995
|
+
label: 'Inspect host availability',
|
|
996
|
+
reason: `${executableForHost(surface.host)} is not installed or is not on PATH.`,
|
|
997
|
+
});
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (surface.host === 'codex' && surface.action === 'update') {
|
|
1001
|
+
const marketplace = await inspectMarketplace(execute);
|
|
1002
|
+
if (marketplace.type === 'local') {
|
|
1003
|
+
const local = await inspectLocalMarketplace(marketplace.root, execute);
|
|
1004
|
+
if (!local.clean || !local.official || local.branch !== 'main') {
|
|
1005
|
+
const reasons = [
|
|
1006
|
+
!local.clean ? 'it has uncommitted changes' : '',
|
|
1007
|
+
!local.official ? 'its origin is not the official EAI Gofer repository' : '',
|
|
1008
|
+
local.branch !== 'main' ? `it is on ${local.branch || 'a detached branch'}, not main` : '',
|
|
1009
|
+
].filter(Boolean).join('; ');
|
|
1010
|
+
results.push({
|
|
1011
|
+
host: surface.host,
|
|
1012
|
+
label: 'Update local EAI Gofer marketplace',
|
|
1013
|
+
ok: false,
|
|
1014
|
+
error:
|
|
1015
|
+
`EAI Gofer uses the local marketplace at ${sanitizeLocalPath(marketplace.root)}, but it was not updated because ${sanitizeDiagnostic(reasons)}. ` +
|
|
1016
|
+
'The always-on EAI instruction was refreshed. Commit, stash, or switch the local checkout to a clean official main branch, or replace it with the public Git marketplace, then restart Codex.',
|
|
1017
|
+
});
|
|
1018
|
+
planFailed = true;
|
|
1019
|
+
configuredHosts.push(surface.host);
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
surface.commands = [
|
|
1023
|
+
command('git', ['-C', marketplace.root, 'fetch', 'origin', 'main'], 'Fetch the local EAI Gofer marketplace'),
|
|
1024
|
+
command('git', ['-C', marketplace.root, 'merge', '--ff-only', 'origin/main'], 'Fast-forward the local EAI Gofer marketplace'),
|
|
1025
|
+
command('codex', ['plugin', 'add', 'eai-gofer@eai-gofer'], 'Apply the refreshed EAI Gofer plugin'),
|
|
1026
|
+
];
|
|
1027
|
+
}
|
|
1028
|
+
if (marketplace.type !== 'git' && marketplace.type !== 'local') {
|
|
1029
|
+
results.push({
|
|
1030
|
+
host: surface.host,
|
|
1031
|
+
label: 'Inspect Codex EAI Gofer marketplace',
|
|
1032
|
+
ok: false,
|
|
1033
|
+
error:
|
|
1034
|
+
'Could not confirm the Codex marketplace source. Update stopped to protect local Gofer work and settings.',
|
|
1035
|
+
});
|
|
1036
|
+
planFailed = true;
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
let completedSurface = true;
|
|
1041
|
+
for (const step of surface.commands) {
|
|
1042
|
+
try {
|
|
1043
|
+
const result = await execute(step.command, step.args, { windowsHide: true });
|
|
1044
|
+
results.push({
|
|
1045
|
+
host: surface.host,
|
|
1046
|
+
label: step.label,
|
|
1047
|
+
ok: true,
|
|
1048
|
+
stdout: sanitizeDiagnostic(result.stdout.trim()),
|
|
1049
|
+
});
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
results.push({
|
|
1052
|
+
host: surface.host,
|
|
1053
|
+
label: step.label,
|
|
1054
|
+
ok: false,
|
|
1055
|
+
error: sanitizeDiagnostic(error?.stderr?.trim() ?? error?.message ?? String(error)),
|
|
1056
|
+
});
|
|
1057
|
+
completedSurface = false;
|
|
1058
|
+
planFailed = true;
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
completedUpdate ||= completedSurface && surface.commands.length > 0;
|
|
1063
|
+
if (completedSurface && surface.commands.length > 0) {
|
|
1064
|
+
configuredHosts.push(surface.host);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (configuredHosts.length > 0) {
|
|
1069
|
+
const instructionResults = await configureInstructions(configuredHosts);
|
|
1070
|
+
for (const entry of instructionResults) {
|
|
1071
|
+
if (!entry.ok) planFailed = true;
|
|
1072
|
+
results.push({
|
|
1073
|
+
host: entry.host,
|
|
1074
|
+
label: entry.managedByPlugin
|
|
1075
|
+
? 'Verify bundled always-on Gofer skill'
|
|
1076
|
+
: 'Enable always-on Gofer instructions',
|
|
1077
|
+
ok: entry.ok,
|
|
1078
|
+
targetPath: sanitizeLocalPath(entry.targetPath),
|
|
1079
|
+
error: sanitizeDiagnostic(entry.error),
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
if (completedUpdate && !planFailed) {
|
|
1085
|
+
try {
|
|
1086
|
+
const report = await cleanup({ apply: true });
|
|
1087
|
+
results.push({
|
|
1088
|
+
host: 'local',
|
|
1089
|
+
label: 'Archive stale Gofer surface entries',
|
|
1090
|
+
ok: true,
|
|
1091
|
+
archived: report.removed.length,
|
|
1092
|
+
archiveRoot: sanitizeLocalPath(report.archiveRoot),
|
|
1093
|
+
});
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
results.push({
|
|
1096
|
+
host: 'local',
|
|
1097
|
+
label: 'Archive stale Gofer surface entries',
|
|
1098
|
+
ok: false,
|
|
1099
|
+
error: sanitizeDiagnostic(error instanceof Error ? error.message : String(error)),
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
return results;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
export function formatSurfaceUpdateReport(result) {
|
|
1108
|
+
result = sanitizeSurfaceUpdateOutput(result);
|
|
1109
|
+
const lines = [
|
|
1110
|
+
`Action: ${result.action}`,
|
|
1111
|
+
`Mode: ${result.execute ? 'execute' : 'plan only'}`,
|
|
1112
|
+
];
|
|
1113
|
+
|
|
1114
|
+
if (result.action === 'inspect') {
|
|
1115
|
+
for (const host of result.hosts) {
|
|
1116
|
+
const status = host.available
|
|
1117
|
+
? `available${host.installed ? ', Gofer installed' : ', Gofer not installed'}`
|
|
1118
|
+
: `not available${host.error ? `: ${host.error}` : ''}`;
|
|
1119
|
+
lines.push(`${host.host}: ${status}`);
|
|
1120
|
+
}
|
|
1121
|
+
return lines.join('\n');
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
for (const surface of result.plan) {
|
|
1125
|
+
lines.push(`${surface.host}: ${surface.commands.map((step) => step.label).join('; ')}`);
|
|
1126
|
+
lines.push(`${surface.host}: reload - ${surface.refresh}`);
|
|
1127
|
+
}
|
|
1128
|
+
for (const entry of result.results) {
|
|
1129
|
+
if (entry.skipped) lines.push(`${entry.host}: skipped - ${entry.reason}`);
|
|
1130
|
+
else if (entry.ok) lines.push(`${entry.host}: ${entry.label} completed${entry.note ? ` - ${entry.note}` : ''}`);
|
|
1131
|
+
else lines.push(`${entry.host}: ${entry.label} failed - ${entry.error}`);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
return lines.join('\n');
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function printUsage() {
|
|
1138
|
+
process.stdout.write(`Usage: node gofer-surface-update.mjs --action <inspect|install|update> --host <${SUPPORTED_HOSTS.join('|')}|all> [--execute] [--json]\n`);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
async function main() {
|
|
1142
|
+
const args = parseArgs(process.argv.slice(2));
|
|
1143
|
+
if (args.help) {
|
|
1144
|
+
printUsage();
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const selectedHosts = resolveHosts(args.host);
|
|
1148
|
+
if (selectedHosts.length === 0) {
|
|
1149
|
+
throw new Error('Use --host when running this helper outside a Gofer surface.');
|
|
1150
|
+
}
|
|
1151
|
+
if (args.action === 'inspect') {
|
|
1152
|
+
const report = await inspectHosts(selectedHosts);
|
|
1153
|
+
const result = { action: 'inspect', execute: false, hosts: report };
|
|
1154
|
+
process.stdout.write(
|
|
1155
|
+
`${args.json ? JSON.stringify(sanitizeSurfaceUpdateOutput(result), null, 2) : formatSurfaceUpdateReport(result)}\n`
|
|
1156
|
+
);
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const plan = buildSurfacePlan(args);
|
|
1160
|
+
const result = {
|
|
1161
|
+
action: args.action,
|
|
1162
|
+
execute: args.execute,
|
|
1163
|
+
plan,
|
|
1164
|
+
results: args.execute ? await runPlan(plan) : [],
|
|
1165
|
+
};
|
|
1166
|
+
process.stdout.write(
|
|
1167
|
+
`${args.json ? JSON.stringify(sanitizeSurfaceUpdateOutput(result), null, 2) : formatSurfaceUpdateReport(result)}\n`
|
|
1168
|
+
);
|
|
1169
|
+
if (args.execute && result.results.some((entry) => entry.ok === false)) process.exitCode = 1;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : '';
|
|
1173
|
+
if (fileURLToPath(import.meta.url) === invokedPath) {
|
|
1174
|
+
main().catch((error) => {
|
|
1175
|
+
process.stderr.write(`${sanitizeDiagnostic(error instanceof Error ? error.message : String(error))}\n`);
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
});
|
|
1178
|
+
}
|