@raidiant/notifai 11.7.1 → 12.0.0-beta.11
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/CHANGELOG.md +53 -0
- package/NOTICE +16 -0
- package/THIRD_PARTY_NOTICES +725 -0
- package/bin/notifai.mjs +11420 -0
- package/data/install.ps1 +318 -0
- package/data/release.json +1 -0
- package/inventory.json +1 -0
- package/npm-adapter-files.json +52 -0
- package/package.json +14 -44
- package/README.md +0 -168
- package/dist/agent-update-notice.d.ts +0 -7
- package/dist/agent-update-notice.js +0 -77
- package/dist/agent-update-notice.js.map +0 -1
- package/dist/atomic-file.d.ts +0 -30
- package/dist/atomic-file.js +0 -148
- package/dist/atomic-file.js.map +0 -1
- package/dist/attendant-update.d.ts +0 -15
- package/dist/attendant-update.js +0 -96
- package/dist/attendant-update.js.map +0 -1
- package/dist/claude-wake.d.ts +0 -108
- package/dist/claude-wake.js +0 -369
- package/dist/claude-wake.js.map +0 -1
- package/dist/cli-bin.d.ts +0 -26
- package/dist/cli-bin.js +0 -220
- package/dist/cli-bin.js.map +0 -1
- package/dist/cli-contract.d.ts +0 -16
- package/dist/cli-contract.js +0 -27
- package/dist/cli-contract.js.map +0 -1
- package/dist/cli-release.d.ts +0 -36
- package/dist/cli-release.js +0 -85
- package/dist/cli-release.js.map +0 -1
- package/dist/client.d.ts +0 -109
- package/dist/client.js +0 -217
- package/dist/client.js.map +0 -1
- package/dist/codex-answer-control.d.ts +0 -14
- package/dist/codex-answer-control.js +0 -41
- package/dist/codex-answer-control.js.map +0 -1
- package/dist/codex-answer-presentation.d.ts +0 -18
- package/dist/codex-answer-presentation.js +0 -113
- package/dist/codex-answer-presentation.js.map +0 -1
- package/dist/codex-input-lifecycle.d.ts +0 -10
- package/dist/codex-input-lifecycle.js +0 -63
- package/dist/codex-input-lifecycle.js.map +0 -1
- package/dist/codex-native-control.d.ts +0 -13
- package/dist/codex-native-control.js +0 -120
- package/dist/codex-native-control.js.map +0 -1
- package/dist/codex-native-turn.d.ts +0 -37
- package/dist/codex-native-turn.js +0 -155
- package/dist/codex-native-turn.js.map +0 -1
- package/dist/codex-question-bindings.d.ts +0 -76
- package/dist/codex-question-bindings.js +0 -151
- package/dist/codex-question-bindings.js.map +0 -1
- package/dist/codex-queue-control.d.ts +0 -21
- package/dist/codex-queue-control.js +0 -59
- package/dist/codex-queue-control.js.map +0 -1
- package/dist/codex-tool-messages.d.ts +0 -7
- package/dist/codex-tool-messages.js +0 -126
- package/dist/codex-tool-messages.js.map +0 -1
- package/dist/codex-wake.d.ts +0 -58
- package/dist/codex-wake.js +0 -135
- package/dist/codex-wake.js.map +0 -1
- package/dist/command-session.d.ts +0 -14
- package/dist/command-session.js +0 -31
- package/dist/command-session.js.map +0 -1
- package/dist/commands-acknowledge.d.ts +0 -13
- package/dist/commands-acknowledge.js +0 -116
- package/dist/commands-acknowledge.js.map +0 -1
- package/dist/commands-agent-sessions.d.ts +0 -6
- package/dist/commands-agent-sessions.js +0 -51
- package/dist/commands-agent-sessions.js.map +0 -1
- package/dist/commands-ask.d.ts +0 -62
- package/dist/commands-ask.js +0 -503
- package/dist/commands-ask.js.map +0 -1
- package/dist/commands-auth.d.ts +0 -76
- package/dist/commands-auth.js +0 -421
- package/dist/commands-auth.js.map +0 -1
- package/dist/commands-close.d.ts +0 -6
- package/dist/commands-close.js +0 -273
- package/dist/commands-close.js.map +0 -1
- package/dist/commands-config.d.ts +0 -31
- package/dist/commands-config.js +0 -272
- package/dist/commands-config.js.map +0 -1
- package/dist/commands-core.d.ts +0 -145
- package/dist/commands-core.js +0 -190
- package/dist/commands-core.js.map +0 -1
- package/dist/commands-devices.d.ts +0 -41
- package/dist/commands-devices.js +0 -144
- package/dist/commands-devices.js.map +0 -1
- package/dist/commands-doctor.d.ts +0 -25
- package/dist/commands-doctor.js +0 -1553
- package/dist/commands-doctor.js.map +0 -1
- package/dist/commands-guidance.d.ts +0 -27
- package/dist/commands-guidance.js +0 -150
- package/dist/commands-guidance.js.map +0 -1
- package/dist/commands-harness-context.d.ts +0 -57
- package/dist/commands-harness-context.js +0 -102
- package/dist/commands-harness-context.js.map +0 -1
- package/dist/commands-hook-attend.d.ts +0 -70
- package/dist/commands-hook-attend.js +0 -655
- package/dist/commands-hook-attend.js.map +0 -1
- package/dist/commands-hook-diagnostics.d.ts +0 -37
- package/dist/commands-hook-diagnostics.js +0 -136
- package/dist/commands-hook-diagnostics.js.map +0 -1
- package/dist/commands-hook-install.d.ts +0 -26
- package/dist/commands-hook-install.js +0 -784
- package/dist/commands-hook-install.js.map +0 -1
- package/dist/commands-hook-run.d.ts +0 -29
- package/dist/commands-hook-run.js +0 -933
- package/dist/commands-hook-run.js.map +0 -1
- package/dist/commands-hook-shape.d.ts +0 -19
- package/dist/commands-hook-shape.js +0 -48
- package/dist/commands-hook-shape.js.map +0 -1
- package/dist/commands-init.d.ts +0 -42
- package/dist/commands-init.js +0 -901
- package/dist/commands-init.js.map +0 -1
- package/dist/commands-io.d.ts +0 -2
- package/dist/commands-io.js +0 -117
- package/dist/commands-io.js.map +0 -1
- package/dist/commands-logs.d.ts +0 -30
- package/dist/commands-logs.js +0 -201
- package/dist/commands-logs.js.map +0 -1
- package/dist/commands-native-acknowledge.d.ts +0 -8
- package/dist/commands-native-acknowledge.js +0 -114
- package/dist/commands-native-acknowledge.js.map +0 -1
- package/dist/commands-project.d.ts +0 -4
- package/dist/commands-project.js +0 -41
- package/dist/commands-project.js.map +0 -1
- package/dist/commands-receive.d.ts +0 -3
- package/dist/commands-receive.js +0 -59
- package/dist/commands-receive.js.map +0 -1
- package/dist/commands-send-support.d.ts +0 -59
- package/dist/commands-send-support.js +0 -258
- package/dist/commands-send-support.js.map +0 -1
- package/dist/commands-send.d.ts +0 -34
- package/dist/commands-send.js +0 -900
- package/dist/commands-send.js.map +0 -1
- package/dist/commands-setup-proof.d.ts +0 -43
- package/dist/commands-setup-proof.js +0 -134
- package/dist/commands-setup-proof.js.map +0 -1
- package/dist/commands-skill.d.ts +0 -21
- package/dist/commands-skill.js +0 -199
- package/dist/commands-skill.js.map +0 -1
- package/dist/commands-sounds.d.ts +0 -12
- package/dist/commands-sounds.js +0 -35
- package/dist/commands-sounds.js.map +0 -1
- package/dist/commands-update-check.d.ts +0 -6
- package/dist/commands-update-check.js +0 -102
- package/dist/commands-update-check.js.map +0 -1
- package/dist/commands-update-resume.d.ts +0 -7
- package/dist/commands-update-resume.js +0 -143
- package/dist/commands-update-resume.js.map +0 -1
- package/dist/commands-update-skill.d.ts +0 -5
- package/dist/commands-update-skill.js +0 -36
- package/dist/commands-update-skill.js.map +0 -1
- package/dist/commands-update.d.ts +0 -11
- package/dist/commands-update.js +0 -286
- package/dist/commands-update.js.map +0 -1
- package/dist/commands.d.ts +0 -26
- package/dist/commands.js +0 -27
- package/dist/commands.js.map +0 -1
- package/dist/config-schema.d.ts +0 -62
- package/dist/config-schema.js +0 -302
- package/dist/config-schema.js.map +0 -1
- package/dist/config.d.ts +0 -181
- package/dist/config.js +0 -357
- package/dist/config.js.map +0 -1
- package/dist/credentials.d.ts +0 -96
- package/dist/credentials.js +0 -342
- package/dist/credentials.js.map +0 -1
- package/dist/file-lock.d.ts +0 -34
- package/dist/file-lock.js +0 -367
- package/dist/file-lock.js.map +0 -1
- package/dist/generated-session-label.d.ts +0 -2
- package/dist/generated-session-label.js +0 -152
- package/dist/generated-session-label.js.map +0 -1
- package/dist/guidance-content.d.ts +0 -53
- package/dist/guidance-content.js +0 -248
- package/dist/guidance-content.js.map +0 -1
- package/dist/guidance-render.d.ts +0 -25
- package/dist/guidance-render.js +0 -47
- package/dist/guidance-render.js.map +0 -1
- package/dist/guidance.d.ts +0 -69
- package/dist/guidance.js +0 -173
- package/dist/guidance.js.map +0 -1
- package/dist/harness-session-title.d.ts +0 -22
- package/dist/harness-session-title.js +0 -74
- package/dist/harness-session-title.js.map +0 -1
- package/dist/harnesses.d.ts +0 -65
- package/dist/harnesses.js +0 -159
- package/dist/harnesses.js.map +0 -1
- package/dist/hermes-attendant.d.ts +0 -38
- package/dist/hermes-attendant.js +0 -337
- package/dist/hermes-attendant.js.map +0 -1
- package/dist/hermes-plugin.d.ts +0 -11
- package/dist/hermes-plugin.js +0 -299
- package/dist/hermes-plugin.js.map +0 -1
- package/dist/hook-acknowledgements.d.ts +0 -70
- package/dist/hook-acknowledgements.js +0 -490
- package/dist/hook-acknowledgements.js.map +0 -1
- package/dist/hook-adapter.d.ts +0 -48
- package/dist/hook-adapter.js +0 -419
- package/dist/hook-adapter.js.map +0 -1
- package/dist/hook-events.d.ts +0 -105
- package/dist/hook-events.js +0 -130
- package/dist/hook-events.js.map +0 -1
- package/dist/hook-gates.d.ts +0 -7
- package/dist/hook-gates.js +0 -20
- package/dist/hook-gates.js.map +0 -1
- package/dist/hook-input.d.ts +0 -10
- package/dist/hook-input.js +0 -64
- package/dist/hook-input.js.map +0 -1
- package/dist/hook-lifecycle.d.ts +0 -112
- package/dist/hook-lifecycle.js +0 -2031
- package/dist/hook-lifecycle.js.map +0 -1
- package/dist/hook-project-sessions.d.ts +0 -28
- package/dist/hook-project-sessions.js +0 -141
- package/dist/hook-project-sessions.js.map +0 -1
- package/dist/hook-question-lock.d.ts +0 -29
- package/dist/hook-question-lock.js +0 -217
- package/dist/hook-question-lock.js.map +0 -1
- package/dist/hook-question-retirement.d.ts +0 -74
- package/dist/hook-question-retirement.js +0 -314
- package/dist/hook-question-retirement.js.map +0 -1
- package/dist/hook-question-state.d.ts +0 -58
- package/dist/hook-question-state.js +0 -239
- package/dist/hook-question-state.js.map +0 -1
- package/dist/hook-session-state.d.ts +0 -127
- package/dist/hook-session-state.js +0 -488
- package/dist/hook-session-state.js.map +0 -1
- package/dist/hook-types.d.ts +0 -476
- package/dist/hook-types.js +0 -4
- package/dist/hook-types.js.map +0 -1
- package/dist/injection-render.d.ts +0 -36
- package/dist/injection-render.js +0 -61
- package/dist/injection-render.js.map +0 -1
- package/dist/install-hooks.d.ts +0 -470
- package/dist/install-hooks.js +0 -1622
- package/dist/install-hooks.js.map +0 -1
- package/dist/integration-health.d.ts +0 -26
- package/dist/integration-health.js +0 -133
- package/dist/integration-health.js.map +0 -1
- package/dist/interactive.d.ts +0 -22
- package/dist/interactive.js +0 -644
- package/dist/interactive.js.map +0 -1
- package/dist/invocation-context.d.ts +0 -47
- package/dist/invocation-context.js +0 -164
- package/dist/invocation-context.js.map +0 -1
- package/dist/local-path.d.ts +0 -7
- package/dist/local-path.js +0 -27
- package/dist/local-path.js.map +0 -1
- package/dist/logging.d.ts +0 -157
- package/dist/logging.js +0 -599
- package/dist/logging.js.map +0 -1
- package/dist/main-run.d.ts +0 -1
- package/dist/main-run.js +0 -42
- package/dist/main-run.js.map +0 -1
- package/dist/main.d.ts +0 -2
- package/dist/main.js +0 -12
- package/dist/main.js.map +0 -1
- package/dist/native-answer-operation.d.ts +0 -70
- package/dist/native-answer-operation.js +0 -247
- package/dist/native-answer-operation.js.map +0 -1
- package/dist/native-skills.d.ts +0 -64
- package/dist/native-skills.js +0 -167
- package/dist/native-skills.js.map +0 -1
- package/dist/node-floor.d.ts +0 -19
- package/dist/node-floor.js +0 -32
- package/dist/node-floor.js.map +0 -1
- package/dist/npm-invocation.d.ts +0 -34
- package/dist/npm-invocation.js +0 -53
- package/dist/npm-invocation.js.map +0 -1
- package/dist/openclaw-continuation-bridge.d.ts +0 -7
- package/dist/openclaw-continuation-bridge.js +0 -64
- package/dist/openclaw-continuation-bridge.js.map +0 -1
- package/dist/openclaw-gateway-readiness.d.ts +0 -1
- package/dist/openclaw-gateway-readiness.js +0 -24
- package/dist/openclaw-gateway-readiness.js.map +0 -1
- package/dist/openclaw-generation.d.ts +0 -21
- package/dist/openclaw-generation.js +0 -115
- package/dist/openclaw-generation.js.map +0 -1
- package/dist/openclaw-message-bridge.d.ts +0 -8
- package/dist/openclaw-message-bridge.js +0 -93
- package/dist/openclaw-message-bridge.js.map +0 -1
- package/dist/openclaw-pending.d.ts +0 -8
- package/dist/openclaw-pending.js +0 -47
- package/dist/openclaw-pending.js.map +0 -1
- package/dist/openclaw-plugin.d.ts +0 -75
- package/dist/openclaw-plugin.js +0 -1353
- package/dist/openclaw-plugin.js.map +0 -1
- package/dist/openclaw-session-access.d.ts +0 -12
- package/dist/openclaw-session-access.js +0 -79
- package/dist/openclaw-session-access.js.map +0 -1
- package/dist/opencode-plugin.d.ts +0 -88
- package/dist/opencode-plugin.js +0 -286
- package/dist/opencode-plugin.js.map +0 -1
- package/dist/orca-session-title.d.ts +0 -11
- package/dist/orca-session-title.js +0 -111
- package/dist/orca-session-title.js.map +0 -1
- package/dist/pack-feedback-slice.d.ts +0 -35
- package/dist/pack-feedback-slice.js +0 -126
- package/dist/pack-feedback-slice.js.map +0 -1
- package/dist/pairing-qr.d.ts +0 -5
- package/dist/pairing-qr.js +0 -24
- package/dist/pairing-qr.js.map +0 -1
- package/dist/pending-pairing.d.ts +0 -38
- package/dist/pending-pairing.js +0 -70
- package/dist/pending-pairing.js.map +0 -1
- package/dist/platform.d.ts +0 -67
- package/dist/platform.js +0 -140
- package/dist/platform.js.map +0 -1
- package/dist/process-identity.d.ts +0 -26
- package/dist/process-identity.js +0 -82
- package/dist/process-identity.js.map +0 -1
- package/dist/program.d.ts +0 -59
- package/dist/program.js +0 -691
- package/dist/program.js.map +0 -1
- package/dist/project-enablement.d.ts +0 -21
- package/dist/project-enablement.js +0 -71
- package/dist/project-enablement.js.map +0 -1
- package/dist/question-settlement-process.d.ts +0 -17
- package/dist/question-settlement-process.js +0 -35
- package/dist/question-settlement-process.js.map +0 -1
- package/dist/question-timing.d.ts +0 -46
- package/dist/question-timing.js +0 -48
- package/dist/question-timing.js.map +0 -1
- package/dist/readiness.d.ts +0 -153
- package/dist/readiness.js +0 -149
- package/dist/readiness.js.map +0 -1
- package/dist/release.d.ts +0 -31
- package/dist/release.js +0 -52
- package/dist/release.js.map +0 -1
- package/dist/send-attempts.d.ts +0 -26
- package/dist/send-attempts.js +0 -139
- package/dist/send-attempts.js.map +0 -1
- package/dist/send.d.ts +0 -115
- package/dist/send.js +0 -421
- package/dist/send.js.map +0 -1
- package/dist/session-activation.d.ts +0 -13
- package/dist/session-activation.js +0 -64
- package/dist/session-activation.js.map +0 -1
- package/dist/session-attendant-probe.d.ts +0 -85
- package/dist/session-attendant-probe.js +0 -173
- package/dist/session-attendant-probe.js.map +0 -1
- package/dist/session-attendant-state.d.ts +0 -70
- package/dist/session-attendant-state.js +0 -256
- package/dist/session-attendant-state.js.map +0 -1
- package/dist/session-attendant.d.ts +0 -143
- package/dist/session-attendant.js +0 -520
- package/dist/session-attendant.js.map +0 -1
- package/dist/session-delivery.d.ts +0 -249
- package/dist/session-delivery.js +0 -576
- package/dist/session-delivery.js.map +0 -1
- package/dist/session-handoff.d.ts +0 -98
- package/dist/session-handoff.js +0 -118
- package/dist/session-handoff.js.map +0 -1
- package/dist/session-input-wakes.d.ts +0 -78
- package/dist/session-input-wakes.js +0 -239
- package/dist/session-input-wakes.js.map +0 -1
- package/dist/session-inputs.d.ts +0 -57
- package/dist/session-inputs.js +0 -434
- package/dist/session-inputs.js.map +0 -1
- package/dist/session-labels.d.ts +0 -56
- package/dist/session-labels.js +0 -396
- package/dist/session-labels.js.map +0 -1
- package/dist/session-message-handoff.d.ts +0 -39
- package/dist/session-message-handoff.js +0 -107
- package/dist/session-message-handoff.js.map +0 -1
- package/dist/setup-destinations.d.ts +0 -23
- package/dist/setup-destinations.js +0 -44
- package/dist/setup-destinations.js.map +0 -1
- package/dist/skill-integrity.d.ts +0 -54
- package/dist/skill-integrity.js +0 -164
- package/dist/skill-integrity.js.map +0 -1
- package/dist/skill-source/manifest.json +0 -40
- package/dist/skill-source/notifai/SKILL.md +0 -350
- package/dist/skill-source/notifai/references/diagnostics.md +0 -36
- package/dist/skill-source/notifai/references/harness-setup.md +0 -384
- package/dist/skill-source/notifai/references/native-questions.md +0 -51
- package/dist/skill-source/notifai/references/notes-and-edits.md +0 -54
- package/dist/skill-source/notifai/references/send-details.md +0 -14
- package/dist/skill-source/notifai/references/updates.md +0 -116
- package/dist/skill-source/notifai/references/writing-examples.md +0 -56
- package/dist/sound-ref.d.ts +0 -23
- package/dist/sound-ref.js +0 -28
- package/dist/sound-ref.js.map +0 -1
- package/dist/ui/banner.d.ts +0 -22
- package/dist/ui/banner.js +0 -87
- package/dist/ui/banner.js.map +0 -1
- package/dist/ui/config-view.d.ts +0 -30
- package/dist/ui/config-view.js +0 -108
- package/dist/ui/config-view.js.map +0 -1
- package/dist/ui/help.d.ts +0 -44
- package/dist/ui/help.js +0 -107
- package/dist/ui/help.js.map +0 -1
- package/dist/ui/theme.d.ts +0 -90
- package/dist/ui/theme.js +0 -244
- package/dist/ui/theme.js.map +0 -1
- package/dist/update-handoff.d.ts +0 -29
- package/dist/update-handoff.js +0 -69
- package/dist/update-handoff.js.map +0 -1
- package/dist/url-policy.d.ts +0 -85
- package/dist/url-policy.js +0 -369
- package/dist/url-policy.js.map +0 -1
- package/dist/version.d.ts +0 -40
- package/dist/version.js +0 -132
- package/dist/version.js.map +0 -1
- package/dist/wake-support.d.ts +0 -40
- package/dist/wake-support.js +0 -95
- package/dist/wake-support.js.map +0 -1
package/dist/commands-init.js
DELETED
|
@@ -1,901 +0,0 @@
|
|
|
1
|
-
import { CAPABILITIES_V1, validateDraft, } from '@raidiant/notifai-protocol';
|
|
2
|
-
import { randomBytes } from 'node:crypto';
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { parse as parseToml, stringify as stringifyToml } from 'smol-toml';
|
|
6
|
-
import { ApiCallError, NetworkError } from './client.js';
|
|
7
|
-
import { sharedProjectConfigPath } from './config.js';
|
|
8
|
-
import { projectSlugFrom as inferredProjectSlugFrom } from './invocation-context.js';
|
|
9
|
-
import { conventionalSkillPath } from './native-skills.js';
|
|
10
|
-
import { canonicalPath } from './local-path.js';
|
|
11
|
-
import { firstRequiredBlocker, isOptionalAutomation, isOptionalSetup, questionRoutingReady, readinessJson, } from './readiness.js';
|
|
12
|
-
import { buildDraft, formatReceipt } from './send.js';
|
|
13
|
-
import { loginCommand } from './commands-auth.js';
|
|
14
|
-
import { EXIT, SETUP_COMMAND, authedClient, loadLoggedConfig, reportError, updateCliCommand, } from './commands-core.js';
|
|
15
|
-
import { readyCompanionDevices } from './commands-devices.js';
|
|
16
|
-
import { companionPlatformLabel, setupCompanionUrl, } from './setup-destinations.js';
|
|
17
|
-
import { assessReadiness, remedyLine } from './commands-doctor.js';
|
|
18
|
-
import { hooksInstallCommand, pickHarnessesToInstall } from './commands-hook-install.js';
|
|
19
|
-
import { observedCompanionReceipt, observedSetupProof, readSetupProof, setupProofApplies, setupProofIsStale, writeSetupProof, } from './commands-setup-proof.js';
|
|
20
|
-
import { installedSkillMatchesPackage, listScopedNotifaiSkills, SKILLS_SOURCE } from './commands-skill.js';
|
|
21
|
-
import { enableProject, projectBinding } from './project-enablement.js';
|
|
22
|
-
import { inspectCliInstallations } from './cli-bin.js';
|
|
23
|
-
import { installHookAdapter } from './hook-adapter.js';
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// init
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
/** Derive a contract-valid project slug; init alone needs a non-empty fallback. */
|
|
28
|
-
export function projectSlugFrom(name) {
|
|
29
|
-
return inferredProjectSlugFrom(name) ?? 'project';
|
|
30
|
-
}
|
|
31
|
-
/** Long enough for a first controlled Companion install; keep-waiting extends another budget. */
|
|
32
|
-
const DEVICE_BRIDGE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
33
|
-
const DEVICE_BRIDGE_POLL_MS = 2_000;
|
|
34
|
-
const PROOF_TIMEOUT_MS = 30_000;
|
|
35
|
-
const PROOF_POLL_MS = 1_000;
|
|
36
|
-
function formatWaitBudget(milliseconds) {
|
|
37
|
-
const minutes = Math.round(milliseconds / 60_000);
|
|
38
|
-
return minutes === 1 ? '1 minute' : `${minutes} minutes`;
|
|
39
|
-
}
|
|
40
|
-
async function closeGap(deps, state, flags) {
|
|
41
|
-
if (state.id === 'project-enablement') {
|
|
42
|
-
const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env });
|
|
43
|
-
const binding = projectBinding(deps.cwd, deps.env, config.project.value);
|
|
44
|
-
if (binding === null)
|
|
45
|
-
return 'failed';
|
|
46
|
-
enableProject(binding);
|
|
47
|
-
return 'closed';
|
|
48
|
-
}
|
|
49
|
-
if (state.id === 'hooks') {
|
|
50
|
-
const harnesses = await pickHarnessesToInstall(deps);
|
|
51
|
-
if (harnesses === null || harnesses.length === 0)
|
|
52
|
-
return 'failed';
|
|
53
|
-
let ok = true;
|
|
54
|
-
for (const harness of harnesses) {
|
|
55
|
-
if (hooksInstallCommand(deps, { harness, narrate: false }) !== EXIT.ok)
|
|
56
|
-
ok = false;
|
|
57
|
-
}
|
|
58
|
-
return ok ? 'closed' : 'failed';
|
|
59
|
-
}
|
|
60
|
-
if (state.id === 'hooks-adapter') {
|
|
61
|
-
const effective = inspectCliInstallations(deps.env, deps.hookPlatform ?? process.platform).effective;
|
|
62
|
-
if (effective === null || effective.artifact_path === null)
|
|
63
|
-
return 'failed';
|
|
64
|
-
try {
|
|
65
|
-
installHookAdapter({ execPath: process.execPath, scriptPath: effective.artifact_path }, deps.hookAdapterHome, deps.hookPlatform ?? process.platform);
|
|
66
|
-
return 'closed';
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
deps.io.err(`Could not retarget Question Routing: ${String(err)}`);
|
|
70
|
-
return 'failed';
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (state.id === 'skill') {
|
|
74
|
-
if (deps.nativeSkills === undefined) {
|
|
75
|
-
deps.io.err('Skill installation failed — the native `npx skills` flow is unavailable.');
|
|
76
|
-
return 'failed';
|
|
77
|
-
}
|
|
78
|
-
// Refuse rather than guess a release identity. The production adapter uses
|
|
79
|
-
// this only as a human label; it verifies and stages npm's bundled skill at
|
|
80
|
-
// a project-relative local path before it invokes the installer.
|
|
81
|
-
if (SKILLS_SOURCE === null) {
|
|
82
|
-
deps.io.err('Skill installation failed — this build cannot determine its own version, so there is no release tag to install from.');
|
|
83
|
-
return 'failed';
|
|
84
|
-
}
|
|
85
|
-
const installScope = flags.skillsScope;
|
|
86
|
-
if (installScope === undefined) {
|
|
87
|
-
deps.io.err('Skill installation refused — choose a skill scope: `notifai init --skills --skills-scope project` or `... global`.');
|
|
88
|
-
return 'failed';
|
|
89
|
-
}
|
|
90
|
-
const retry = `notifai init --skills --skills-scope ${installScope}`;
|
|
91
|
-
let inventory = await listScopedNotifaiSkills(deps);
|
|
92
|
-
if (inventory.errors.length > 0) {
|
|
93
|
-
deps.io.err(`Could not inspect installed guidance. Retry with \`${retry}\`.`);
|
|
94
|
-
return 'failed';
|
|
95
|
-
}
|
|
96
|
-
const scopesOverlap = () => {
|
|
97
|
-
const selected = inventory.installed.find((skill) => skill.scope === installScope)?.path
|
|
98
|
-
?? conventionalSkillPath(installScope, 'notifai', deps.cwd, deps.env);
|
|
99
|
-
const normalize = (file) => {
|
|
100
|
-
const resolved = canonicalPath(file);
|
|
101
|
-
return (deps.hookPlatform ?? process.platform) === 'win32' ? resolved.toLowerCase() : resolved;
|
|
102
|
-
};
|
|
103
|
-
const destination = normalize(selected);
|
|
104
|
-
return inventory.installed.some((skill) => {
|
|
105
|
-
if (skill.scope === installScope)
|
|
106
|
-
return false;
|
|
107
|
-
const old = normalize(skill.path);
|
|
108
|
-
return destination === old || destination.startsWith(`${old}${path.sep}`)
|
|
109
|
-
|| old.startsWith(`${destination}${path.sep}`);
|
|
110
|
-
});
|
|
111
|
-
};
|
|
112
|
-
const refuseOverlap = () => {
|
|
113
|
-
deps.io.err('Project and global skill destinations overlap. Keep the existing scope, or run setup from a project with separate skill directories before changing scope.');
|
|
114
|
-
return 'failed';
|
|
115
|
-
};
|
|
116
|
-
if (scopesOverlap())
|
|
117
|
-
return refuseOverlap();
|
|
118
|
-
const selectedIsVerified = () => inventory.installed.some((skill) => skill.scope === installScope && installedSkillMatchesPackage(skill));
|
|
119
|
-
// Keep the existing scope until the native installer has produced a
|
|
120
|
-
// content-verified replacement. A retry after partial cleanup can reuse
|
|
121
|
-
// this proof and finish without invoking the installer again.
|
|
122
|
-
if (!selectedIsVerified()) {
|
|
123
|
-
deps.io.out(`Starting the native npx skills setup for the notifai agent skill (${installScope} scope)...`);
|
|
124
|
-
const operation = await deps.nativeSkills.add({
|
|
125
|
-
source: SKILLS_SOURCE,
|
|
126
|
-
skill: 'notifai',
|
|
127
|
-
cwd: deps.cwd,
|
|
128
|
-
env: deps.env,
|
|
129
|
-
scope: installScope,
|
|
130
|
-
}).catch((error) => ({ code: 1, error: String(error) }));
|
|
131
|
-
const code = typeof operation === 'number' ? operation : operation.code;
|
|
132
|
-
if (code !== 0) {
|
|
133
|
-
deps.io.err(typeof operation === 'number'
|
|
134
|
-
? `Skill installation failed. Retry with \`${retry}\` after checking the network connection.`
|
|
135
|
-
: `Skill installation refused — ${operation.error}. Retry with \`${retry}\`.`);
|
|
136
|
-
return 'failed';
|
|
137
|
-
}
|
|
138
|
-
inventory = await listScopedNotifaiSkills(deps);
|
|
139
|
-
if (inventory.errors.length > 0 || !selectedIsVerified()) {
|
|
140
|
-
deps.io.err(`The selected skill could not be verified; the previous scope was kept. Retry with \`${retry}\`.`);
|
|
141
|
-
return 'failed';
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
const extras = inventory.installed.filter((skill) => skill.scope !== installScope);
|
|
145
|
-
if (scopesOverlap())
|
|
146
|
-
return refuseOverlap();
|
|
147
|
-
for (const extra of extras) {
|
|
148
|
-
const code = await deps.nativeSkills.remove({
|
|
149
|
-
skill: 'notifai',
|
|
150
|
-
scope: extra.scope,
|
|
151
|
-
cwd: deps.cwd,
|
|
152
|
-
env: deps.env,
|
|
153
|
-
}).catch(() => 1);
|
|
154
|
-
if (code !== 0) {
|
|
155
|
-
deps.io.err(`The ${installScope} skill is verified, but cleanup of the ${extra.scope} copy failed. Retry with \`${retry}\` to finish.`);
|
|
156
|
-
return 'failed';
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
inventory = await listScopedNotifaiSkills(deps);
|
|
160
|
-
if (inventory.errors.length > 0 || inventory.installed.length !== 1 || !selectedIsVerified()) {
|
|
161
|
-
deps.io.err(`Skill migration is incomplete. Retry with \`${retry}\` to finish.`);
|
|
162
|
-
return 'failed';
|
|
163
|
-
}
|
|
164
|
-
return 'closed';
|
|
165
|
-
}
|
|
166
|
-
if (state.id === 'proof')
|
|
167
|
-
return await runSetupProof(deps);
|
|
168
|
-
return 'failed';
|
|
169
|
-
}
|
|
170
|
-
function deviceBridgeMessage(devices) {
|
|
171
|
-
if (devices.length === 0) {
|
|
172
|
-
return 'Waiting for a Companion App to sign in and register…';
|
|
173
|
-
}
|
|
174
|
-
const denied = devices.find((device) => device.permission_status === 'denied');
|
|
175
|
-
if (denied)
|
|
176
|
-
return `Waiting for notifications to be allowed on ${denied.display_name}…`;
|
|
177
|
-
const undecided = devices.find((device) => device.permission_status === 'not_determined');
|
|
178
|
-
if (undecided)
|
|
179
|
-
return `Waiting for ${undecided.display_name} to allow the notification prompt…`;
|
|
180
|
-
return 'Waiting for a Companion device to become ready…';
|
|
181
|
-
}
|
|
182
|
-
/** How many Companion Apps this Account has registered, on any platform. */
|
|
183
|
-
async function registeredCompanions(client) {
|
|
184
|
-
try {
|
|
185
|
-
const response = await client.listDevices();
|
|
186
|
-
return response.devices.filter((device) => device.platform === 'ios' || device.platform === 'android').length;
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
// Unreachable here means unreachable for the wait that follows; ask, so a
|
|
190
|
-
// User with nothing installed still gets the platform-specific steps.
|
|
191
|
-
return 0;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Which phone the notifications should arrive on.
|
|
196
|
-
*
|
|
197
|
-
* Asked in the User's words, here, so the destination this terminal opens is
|
|
198
|
-
* already about their phone rather than an index they have to choose from
|
|
199
|
-
* again on the other screen. This selection is only offered at a human terminal.
|
|
200
|
-
*/
|
|
201
|
-
async function askCompanionPlatform(deps) {
|
|
202
|
-
if (deps.io.interactive !== true || deps.io.select === undefined)
|
|
203
|
-
return null;
|
|
204
|
-
const selected = await deps.io.select('Where do you want to receive notifications?', [
|
|
205
|
-
{ value: 'iphone', label: 'iPhone' },
|
|
206
|
-
{ value: 'android', label: 'Android' },
|
|
207
|
-
]);
|
|
208
|
-
return selected === 'iphone' || selected === 'android' ? selected : null;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Observe the supported Device Installation path while the user finishes the
|
|
212
|
-
* app-side work. The bridge is one focused setup destination for the platform
|
|
213
|
-
* they just named — not the omnibus help page, whose own next step is to go
|
|
214
|
-
* somewhere else. Interactive runs open it so the user never types
|
|
215
|
-
* the URL; non-interactive/agent paths only print plain text and never wait on
|
|
216
|
-
* a prompt.
|
|
217
|
-
*
|
|
218
|
-
* The wait budget is stated up front. On expiry we say the *timer* expired —
|
|
219
|
-
* not the setup — and offer another budget when a human is present. Agents
|
|
220
|
-
* never hang: this whole path is gated on `io.interactive`, and keep-waiting
|
|
221
|
-
* uses `confirm` which resolves the safe default when not interactive.
|
|
222
|
-
*/
|
|
223
|
-
async function waitForReadyDevice(deps, state) {
|
|
224
|
-
const remedy = state.remedy;
|
|
225
|
-
if (deps.io.interactive !== true || remedy?.by !== 'user-elsewhere')
|
|
226
|
-
return 'pending';
|
|
227
|
-
const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env });
|
|
228
|
-
const authed = authedClient(deps, config);
|
|
229
|
-
if (!authed) {
|
|
230
|
-
deps.io.err('Could not start the companion-device wait: this machine is not signed in.');
|
|
231
|
-
return 'failed';
|
|
232
|
-
}
|
|
233
|
-
const budgetLabel = formatWaitBudget(DEVICE_BRIDGE_TIMEOUT_MS);
|
|
234
|
-
// A phone that is registered but silent needs its permission allowed, not
|
|
235
|
-
// install steps — and asking which phone would be asking about one they are
|
|
236
|
-
// already holding. Only a User with nothing registered has a platform still
|
|
237
|
-
// to name, and that is read from the account rather than from the wording of
|
|
238
|
-
// the state that got us here.
|
|
239
|
-
const needsPlatform = (await registeredCompanions(authed.client)) === 0;
|
|
240
|
-
const platform = needsPlatform ? await askCompanionPlatform(deps) : null;
|
|
241
|
-
if (needsPlatform && platform === null)
|
|
242
|
-
return 'pending';
|
|
243
|
-
const setupUrl = setupCompanionUrl(authed.baseUrl, platform ?? undefined);
|
|
244
|
-
const stepsLabel = platform === null ? 'Setup steps (no typing)' : `${companionPlatformLabel(platform)} steps (no typing)`;
|
|
245
|
-
await deps.io.note?.([
|
|
246
|
-
state.detail,
|
|
247
|
-
remedy.summary,
|
|
248
|
-
`${stepsLabel}: ${setupUrl}`,
|
|
249
|
-
`I will wait up to ${budgetLabel} for a Companion App to become ready. Ctrl-C stops this wait.`,
|
|
250
|
-
].join('\n'), 'Finish setup on your phone');
|
|
251
|
-
// The platform choice (or resuming an existing installation) starts the
|
|
252
|
-
// handoff. Only extending an expired wait needs another decision.
|
|
253
|
-
deps.io.openUrl(setupUrl);
|
|
254
|
-
const now = deps.now ?? Date.now;
|
|
255
|
-
const sleep = deps.sleep ??
|
|
256
|
-
((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
257
|
-
const spinner = await deps.io.spinner?.(`Waiting up to ${budgetLabel} for a Companion device…`);
|
|
258
|
-
let lastDevices = [];
|
|
259
|
-
let deadline = now() + DEVICE_BRIDGE_TIMEOUT_MS;
|
|
260
|
-
for (;;) {
|
|
261
|
-
while (now() < deadline) {
|
|
262
|
-
try {
|
|
263
|
-
const response = await authed.client.listDevices();
|
|
264
|
-
const companionDevices = response.devices.filter((device) => device.platform === 'ios' || device.platform === 'android');
|
|
265
|
-
lastDevices = companionDevices;
|
|
266
|
-
const ready = readyCompanionDevices(companionDevices)[0];
|
|
267
|
-
if (ready) {
|
|
268
|
-
spinner?.stop(`${ready.display_name} is ready to receive`);
|
|
269
|
-
return 'closed';
|
|
270
|
-
}
|
|
271
|
-
spinner?.message(deviceBridgeMessage(companionDevices));
|
|
272
|
-
}
|
|
273
|
-
catch (err) {
|
|
274
|
-
if (!(err instanceof NetworkError)) {
|
|
275
|
-
spinner?.error('Could not check companion readiness');
|
|
276
|
-
reportError(deps, err);
|
|
277
|
-
return 'failed';
|
|
278
|
-
}
|
|
279
|
-
spinner?.message('Connection lost — still watching…');
|
|
280
|
-
}
|
|
281
|
-
await sleep(Math.min(DEVICE_BRIDGE_POLL_MS, Math.max(0, deadline - now())));
|
|
282
|
-
}
|
|
283
|
-
// Timer expired — setup did not. Offer another budget only when a human
|
|
284
|
-
// can answer; confirm() already returns the fallback for agents, so this
|
|
285
|
-
// never hangs unattended even if interactive were mis-set.
|
|
286
|
-
spinner?.error(`The ${budgetLabel} wait timer expired`);
|
|
287
|
-
deps.io.err(`The ${budgetLabel} wait timer expired — setup is not finished, only this wait.`);
|
|
288
|
-
deps.io.err(deviceBridgeMessage(lastDevices).replace(/…$/, '.'));
|
|
289
|
-
deps.io.err(`Re-run \`notifai init\` later and it will pick up from here (steps: ${setupUrl}).`);
|
|
290
|
-
const keepWaiting = await deps.io.confirm(`Keep waiting for another ${budgetLabel}?`, false);
|
|
291
|
-
if (!keepWaiting) {
|
|
292
|
-
deps.io.out('Stopping the wait. Companion setup can continue; re-run `notifai init` when ready.');
|
|
293
|
-
return 'pending';
|
|
294
|
-
}
|
|
295
|
-
spinner?.message(`Waiting another ${budgetLabel} for a Companion App…`);
|
|
296
|
-
deadline = now() + DEVICE_BRIDGE_TIMEOUT_MS;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* The notification that says a machine is set up — sent once per Approved
|
|
301
|
-
* Machine, never per Project, so it only ever arrives when setup actually
|
|
302
|
-
* finished somewhere new.
|
|
303
|
-
*
|
|
304
|
-
* It names the machine because that is the news: the User may run agents on
|
|
305
|
-
* several, and "your agents can reach you" from one they set up weeks ago
|
|
306
|
-
* reads as a mystery rather than a milestone. It carries no Project, because
|
|
307
|
-
* nothing about it is Project-specific. It arrives the way their
|
|
308
|
-
* notifications will: `done` is the honest kind, and it lets the server pick
|
|
309
|
-
* the sound through its own kind and Account layers instead of this command
|
|
310
|
-
* stamping one.
|
|
311
|
-
*/
|
|
312
|
-
function setupProofDraft(config, machineName, device) {
|
|
313
|
-
return buildDraft(config, {
|
|
314
|
-
projectless: true,
|
|
315
|
-
title: `${machineName} is set up`,
|
|
316
|
-
summary: `Agents on ${machineName} can now send you notifications. They will arrive like this one.`,
|
|
317
|
-
kind: 'done',
|
|
318
|
-
platform: device.platform,
|
|
319
|
-
device: [device.device_id],
|
|
320
|
-
// Noticeable, not intrusive: a normal banner and sound. Deliberately not
|
|
321
|
-
// `time_sensitive` — a setup confirmation has no business breaking a Focus.
|
|
322
|
-
//
|
|
323
|
-
// Android has no caller-selected interruption level. Explicit null keeps an
|
|
324
|
-
// Apple preference in config from leaking into this Android-only proof.
|
|
325
|
-
level: device.platform === 'ios' ? 'active' : null,
|
|
326
|
-
collapseKey: 'notifai-setup-verification',
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
async function submitSetupProof(deps, client, config, device) {
|
|
330
|
-
const machineName = deps.store.load()?.machineName ?? 'This computer';
|
|
331
|
-
const build = setupProofDraft(config, machineName, device);
|
|
332
|
-
if (!build.ok) {
|
|
333
|
-
deps.io.err(`Could not build the setup verification notification: ${build.error}`);
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
const capabilities = CAPABILITIES_V1.describe(build.platform);
|
|
337
|
-
if (!capabilities) {
|
|
338
|
-
deps.io.err(`No capability contract is available for ${build.platform}.`);
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
const validation = validateDraft(build.draft, capabilities);
|
|
342
|
-
if (!validation.ok) {
|
|
343
|
-
for (const issue of validation.errors) {
|
|
344
|
-
deps.io.err(`Setup verification ${issue.path}: ${issue.message}`);
|
|
345
|
-
}
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
try {
|
|
349
|
-
return await client.submit({
|
|
350
|
-
idempotency_key: `init-${randomBytes(12).toString('base64url')}`,
|
|
351
|
-
draft: build.draft,
|
|
352
|
-
}, config.wait_seconds.value);
|
|
353
|
-
}
|
|
354
|
-
catch (err) {
|
|
355
|
-
reportError(deps, err);
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Send or resume one real setup probe, then wait for a Companion Receipt.
|
|
361
|
-
* Provider Acceptance is intentionally insufficient: it proves a push provider
|
|
362
|
-
* accepted the request, not that a companion process received it.
|
|
363
|
-
*/
|
|
364
|
-
async function runSetupProof(deps) {
|
|
365
|
-
const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env });
|
|
366
|
-
const authed = authedClient(deps, config);
|
|
367
|
-
if (!authed)
|
|
368
|
-
return 'failed';
|
|
369
|
-
let devices;
|
|
370
|
-
try {
|
|
371
|
-
devices = (await authed.client.listDevices()).devices;
|
|
372
|
-
}
|
|
373
|
-
catch (err) {
|
|
374
|
-
reportError(deps, err);
|
|
375
|
-
return 'failed';
|
|
376
|
-
}
|
|
377
|
-
const candidates = readyCompanionDevices(devices);
|
|
378
|
-
const existing = readSetupProof(deps);
|
|
379
|
-
// An observed receipt proved this machine; setup never sends again.
|
|
380
|
-
if (existing?.companion_receipt.state === 'observed')
|
|
381
|
-
return 'closed';
|
|
382
|
-
const existingApplies = setupProofApplies(existing, candidates.map((device) => device.device_id));
|
|
383
|
-
const target = (existingApplies
|
|
384
|
-
? candidates.find((device) => device.device_id === existing.device_id)
|
|
385
|
-
: undefined) ?? candidates[0];
|
|
386
|
-
if (!target) {
|
|
387
|
-
deps.io.err('Setup proof needs a receipt-capable iPhone or Android Companion App.');
|
|
388
|
-
return 'pending';
|
|
389
|
-
}
|
|
390
|
-
let proof = existingApplies && existing.device_id === target.device_id
|
|
391
|
-
? existing
|
|
392
|
-
: null;
|
|
393
|
-
const nowMs = (deps.now ?? Date.now)();
|
|
394
|
-
if (proof !== null && setupProofIsStale(proof, nowMs)) {
|
|
395
|
-
let keepStale = false;
|
|
396
|
-
try {
|
|
397
|
-
const snapshot = await authed.client.evidence(proof.request_id);
|
|
398
|
-
keepStale = observedCompanionReceipt(snapshot, proof.device_id) !== null;
|
|
399
|
-
}
|
|
400
|
-
catch (err) {
|
|
401
|
-
if (!(err instanceof ApiCallError && err.code === 'not_found')) {
|
|
402
|
-
// Unavailable evidence cannot establish that the saved request needs replacing.
|
|
403
|
-
reportError(deps, err);
|
|
404
|
-
return 'failed';
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
if (!keepStale) {
|
|
408
|
-
deps.io.out(`The saved proof ${proof.request_id} is older than 24h without a Companion Receipt; sending a replacement.`);
|
|
409
|
-
proof = null;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
if (proof === null) {
|
|
413
|
-
const receipt = await submitSetupProof(deps, authed.client, config, target);
|
|
414
|
-
if (receipt === null)
|
|
415
|
-
return 'failed';
|
|
416
|
-
if (receipt.overall === 'provider_rejected_all') {
|
|
417
|
-
deps.io.err(formatReceipt(receipt));
|
|
418
|
-
return 'failed';
|
|
419
|
-
}
|
|
420
|
-
proof = {
|
|
421
|
-
request_id: receipt.request_id,
|
|
422
|
-
device_id: target.device_id,
|
|
423
|
-
started_at: new Date((deps.now ?? Date.now)()).toISOString(),
|
|
424
|
-
companion_receipt: { state: 'unknown', observed_at: null },
|
|
425
|
-
};
|
|
426
|
-
if (!writeSetupProof(deps, proof))
|
|
427
|
-
return 'failed';
|
|
428
|
-
deps.io.out(`Verification notification sent to ${target.display_name} (${proof.request_id}).`);
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
deps.io.out(`Checking verification notification ${proof.request_id} again.`);
|
|
432
|
-
}
|
|
433
|
-
const now = deps.now ?? Date.now;
|
|
434
|
-
const sleep = deps.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
435
|
-
const deadline = now() + PROOF_TIMEOUT_MS;
|
|
436
|
-
const spinner = deps.io.interactive === true
|
|
437
|
-
? await deps.io.spinner?.("Waiting for a Companion Receipt (the app's delivery confirmation)…")
|
|
438
|
-
: null;
|
|
439
|
-
let lastError = null;
|
|
440
|
-
let replacedMissingProof = false;
|
|
441
|
-
for (;;) {
|
|
442
|
-
try {
|
|
443
|
-
const snapshot = await authed.client.evidence(proof.request_id);
|
|
444
|
-
const observed = observedCompanionReceipt(snapshot, proof.device_id);
|
|
445
|
-
if (observed) {
|
|
446
|
-
if (!writeSetupProof(deps, observedSetupProof(proof, observed.observedAt))) {
|
|
447
|
-
spinner?.error('Could not save the observed Companion Receipt');
|
|
448
|
-
return 'failed';
|
|
449
|
-
}
|
|
450
|
-
spinner?.stop(`Receipt observed from ${observed.delivery.device_name}`);
|
|
451
|
-
deps.io.out(`Companion Receipt (the app's delivery confirmation) observed from ${observed.delivery.device_name}.`);
|
|
452
|
-
return 'closed';
|
|
453
|
-
}
|
|
454
|
-
lastError = null;
|
|
455
|
-
}
|
|
456
|
-
catch (err) {
|
|
457
|
-
lastError = err;
|
|
458
|
-
if (err instanceof ApiCallError &&
|
|
459
|
-
err.code === 'not_found' &&
|
|
460
|
-
!replacedMissingProof) {
|
|
461
|
-
const receipt = await submitSetupProof(deps, authed.client, config, target);
|
|
462
|
-
if (receipt === null)
|
|
463
|
-
return 'failed';
|
|
464
|
-
if (receipt.overall === 'provider_rejected_all') {
|
|
465
|
-
deps.io.err(formatReceipt(receipt));
|
|
466
|
-
return 'failed';
|
|
467
|
-
}
|
|
468
|
-
proof = {
|
|
469
|
-
request_id: receipt.request_id,
|
|
470
|
-
device_id: target.device_id,
|
|
471
|
-
started_at: new Date(now()).toISOString(),
|
|
472
|
-
companion_receipt: { state: 'unknown', observed_at: null },
|
|
473
|
-
};
|
|
474
|
-
if (!writeSetupProof(deps, proof))
|
|
475
|
-
return 'failed';
|
|
476
|
-
replacedMissingProof = true;
|
|
477
|
-
lastError = null;
|
|
478
|
-
deps.io.out(`The saved proof had expired; sent replacement ${proof.request_id}.`);
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
if (!(err instanceof NetworkError)) {
|
|
482
|
-
spinner?.error('Could not read Companion Receipt evidence');
|
|
483
|
-
reportError(deps, err);
|
|
484
|
-
return 'failed';
|
|
485
|
-
}
|
|
486
|
-
spinner?.message('Connection lost — still checking the same request…');
|
|
487
|
-
}
|
|
488
|
-
if (now() >= deadline)
|
|
489
|
-
break;
|
|
490
|
-
await sleep(Math.min(PROOF_POLL_MS, Math.max(0, deadline - now())));
|
|
491
|
-
}
|
|
492
|
-
spinner?.stop('Delivery confirmation not observed yet');
|
|
493
|
-
if (lastError instanceof NetworkError)
|
|
494
|
-
deps.io.err(lastError.message);
|
|
495
|
-
deps.io.out(`Provider accepted the notification; Companion Receipt (the app's delivery confirmation) for ${proof.request_id} was not observed within ${PROOF_TIMEOUT_MS / 1000}s. ` +
|
|
496
|
-
'Proof may still arrive — re-run `notifai init` and it will re-check this same notification.');
|
|
497
|
-
return 'pending';
|
|
498
|
-
}
|
|
499
|
-
/** Closing a local gap cannot have changed the service, the keychain, or devices. */
|
|
500
|
-
function refreshAfterClose(id) {
|
|
501
|
-
return id === 'project-enablement' || id === 'hooks' || id.startsWith('hooks-') || id === 'skill' || id === 'question-routing-settings'
|
|
502
|
-
? ['local']
|
|
503
|
-
: undefined;
|
|
504
|
-
}
|
|
505
|
-
/** Whether an optional gap should be closed, given flags and who is watching. */
|
|
506
|
-
function wantsOptional(deps, state, flags) {
|
|
507
|
-
// Optional CLI updates are surfaced by doctor or when a missing feature is
|
|
508
|
-
// relevant. Init does not turn them into setup work.
|
|
509
|
-
if (state.id === 'contract')
|
|
510
|
-
return Promise.resolve(false);
|
|
511
|
-
if (state.id === 'project')
|
|
512
|
-
return Promise.resolve(false);
|
|
513
|
-
if (state.id === 'project-enablement')
|
|
514
|
-
return Promise.resolve(true);
|
|
515
|
-
// Every other hook sub-state is a report line about routing, not an errand
|
|
516
|
-
// with a yes/no question of its own; offering the hooks question for one of
|
|
517
|
-
// them would ask about something the answer does not change.
|
|
518
|
-
if (state.id !== 'hooks' && state.id !== 'skill')
|
|
519
|
-
return Promise.resolve(false);
|
|
520
|
-
const explicit = state.id === 'hooks' ? flags.hooks : flags.skills;
|
|
521
|
-
if (explicit !== undefined)
|
|
522
|
-
return Promise.resolve(explicit);
|
|
523
|
-
// An agent is never asked, and never assumed into a change it did not
|
|
524
|
-
// request: silence means no, and the summary says what was skipped.
|
|
525
|
-
if (deps.io.interactive !== true)
|
|
526
|
-
return Promise.resolve(false);
|
|
527
|
-
const question = state.id === 'hooks'
|
|
528
|
-
? 'Install harness hooks, so questions reach your devices when you are away?'
|
|
529
|
-
: 'Install/update the agent guidance skill through the native npx skills flow?';
|
|
530
|
-
return deps.io.confirm(question, true);
|
|
531
|
-
}
|
|
532
|
-
/** The one state whose remedy places files a scope choice is about. */
|
|
533
|
-
function needsSkillScope(state) {
|
|
534
|
-
return state.id === 'skill';
|
|
535
|
-
}
|
|
536
|
-
function isSkillScope(value) {
|
|
537
|
-
return value === 'project' || value === 'global';
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* Skill-scope flags are checked before anything runs, because a contradiction
|
|
541
|
-
* is a usage error and reporting it after half a setup is worse than reporting
|
|
542
|
-
* it now. Answering the question is a separate step (`scopeForSkillInstall`).
|
|
543
|
-
*
|
|
544
|
-
* Lifecycle wiring has no scope: it is installed for this machine, and whether
|
|
545
|
-
* it does anything in a Project is Project Enablement's question.
|
|
546
|
-
*/
|
|
547
|
-
function checkSkillScopeFlags(deps, flags) {
|
|
548
|
-
if (flags.skillsScope !== undefined && !isSkillScope(flags.skillsScope)) {
|
|
549
|
-
deps.io.err('Invalid skill scope. Choose `project` or `global`.');
|
|
550
|
-
return 'usage';
|
|
551
|
-
}
|
|
552
|
-
if (flags.skillsScope !== undefined && flags.skills !== true) {
|
|
553
|
-
deps.io.err('`--skills-scope` requires `--skills`.');
|
|
554
|
-
return 'usage';
|
|
555
|
-
}
|
|
556
|
-
// Asking for an install nobody can be asked about is a usage error, and it
|
|
557
|
-
// is knowable from the flags alone — so it is reported before a setup runs
|
|
558
|
-
// half way rather than after.
|
|
559
|
-
if (flags.skillsScope === undefined && deps.io.interactive !== true && flags.skills === true) {
|
|
560
|
-
deps.io.err('Unattended skill installation requires an explicit scope: pass `--skills-scope project` or `--skills-scope global`.');
|
|
561
|
-
return 'usage';
|
|
562
|
-
}
|
|
563
|
-
return flags.skillsScope;
|
|
564
|
-
}
|
|
565
|
-
/**
|
|
566
|
-
* Where the agent guidance skill should land, asked at the moment it is about
|
|
567
|
-
* to be installed and not before.
|
|
568
|
-
*
|
|
569
|
-
* It used to be the first thing the product ever said, as one question
|
|
570
|
-
* covering skill, hook and shared-config placement, put to someone who had met
|
|
571
|
-
* none of those words. Hooks and config no longer have a scope at all, and
|
|
572
|
-
* what remains belongs to `npx skills`.
|
|
573
|
-
*/
|
|
574
|
-
async function scopeForSkillInstall(deps, flags) {
|
|
575
|
-
if (flags.skillsScope !== undefined)
|
|
576
|
-
return flags.skillsScope;
|
|
577
|
-
if (deps.io.interactive === true && deps.io.select) {
|
|
578
|
-
const selected = await deps.io.select('Should the agent guidance skill be installed for this project only, or for every project on this machine?', [
|
|
579
|
-
{
|
|
580
|
-
value: 'project',
|
|
581
|
-
label: 'This project',
|
|
582
|
-
hint: 'the skill stays in this checkout',
|
|
583
|
-
},
|
|
584
|
-
{
|
|
585
|
-
value: 'global',
|
|
586
|
-
label: 'This machine',
|
|
587
|
-
hint: 'the skill follows you into every repo',
|
|
588
|
-
},
|
|
589
|
-
]);
|
|
590
|
-
if (selected === 'project' || selected === 'global')
|
|
591
|
-
return selected;
|
|
592
|
-
deps.io.err('No skill scope selected. Pass `--skills-scope project` or `--skills-scope global`.');
|
|
593
|
-
return 'declined';
|
|
594
|
-
}
|
|
595
|
-
deps.io.err('Unattended skill installation requires an explicit scope: pass `--skills-scope project` or `--skills-scope global`.');
|
|
596
|
-
return 'declined';
|
|
597
|
-
}
|
|
598
|
-
/**
|
|
599
|
-
* Setup as one step at a time.
|
|
600
|
-
*
|
|
601
|
-
* The old version ran five steps in a fixed order and ended with a list of
|
|
602
|
-
* everything still outstanding. That is a report, and a report is the wrong
|
|
603
|
-
* output here: someone handed five things to do does none of them, and the
|
|
604
|
-
* order was the script's rather than the dependency graph's — it offered to
|
|
605
|
-
* install hooks after a sign-in that had just failed.
|
|
606
|
-
*
|
|
607
|
-
* So this closes what it can, then surfaces exactly one thing, the first that
|
|
608
|
-
* stands in the way. Re-running advances by one. Idempotence stops being a
|
|
609
|
-
* property to preserve and becomes the mechanism: every decision is derived
|
|
610
|
-
* from observed state, so a partial run, a second project, a fresh worktree
|
|
611
|
-
* and a revoked credential are the same code path arriving at different
|
|
612
|
-
* states rather than four branches to enumerate.
|
|
613
|
-
*/
|
|
614
|
-
export async function initCommand(deps, flags) {
|
|
615
|
-
const workingDeps = flags.json === true
|
|
616
|
-
? {
|
|
617
|
-
...deps,
|
|
618
|
-
io: {
|
|
619
|
-
interactive: false,
|
|
620
|
-
out: (line) => deps.io.err(line),
|
|
621
|
-
err: (line) => deps.io.err(line),
|
|
622
|
-
confirm: async () => false,
|
|
623
|
-
openUrl: (url) => deps.io.openUrl(url),
|
|
624
|
-
},
|
|
625
|
-
}
|
|
626
|
-
: deps;
|
|
627
|
-
const scopeFromFlags = checkSkillScopeFlags(workingDeps, flags);
|
|
628
|
-
if (scopeFromFlags === 'usage')
|
|
629
|
-
return EXIT.usage;
|
|
630
|
-
let resolved = { ...flags };
|
|
631
|
-
await workingDeps.io.intro?.('Notifai setup');
|
|
632
|
-
// Project inference is the default and writes nothing. An explicit id is a
|
|
633
|
-
// deliberate shared repository override, authored in this checkout so Git
|
|
634
|
-
// can review and merge it like every other tracked project policy change.
|
|
635
|
-
if (resolved.projectId !== undefined) {
|
|
636
|
-
const configPath = sharedProjectConfigPath(workingDeps.cwd);
|
|
637
|
-
const existing = existsSync(configPath)
|
|
638
|
-
? parseToml(readFileSync(configPath, 'utf8'))
|
|
639
|
-
: {};
|
|
640
|
-
existing['project'] = projectSlugFrom(resolved.projectId);
|
|
641
|
-
mkdirSync(path.dirname(configPath), { recursive: true });
|
|
642
|
-
writeFileSync(configPath, `${stringifyToml(existing)}\n`);
|
|
643
|
-
}
|
|
644
|
-
const skillOpts = () => resolved.skillsScope === undefined ? {} : { skillScope: resolved.skillsScope };
|
|
645
|
-
let readiness = await assessReadiness(workingDeps, { ...skillOpts(), ...(flags.json === true ? { json: true } : {}) });
|
|
646
|
-
const reassess = (refresh) => assessReadiness(workingDeps, {
|
|
647
|
-
...skillOpts(),
|
|
648
|
-
...(flags.json === true ? { json: true } : {}),
|
|
649
|
-
...(refresh === undefined ? {} : { previous: readiness, refresh }),
|
|
650
|
-
});
|
|
651
|
-
let failed = false;
|
|
652
|
-
// What actually stopped a sign-in, when something did. Without it the close
|
|
653
|
-
// renders the state as it was before the attempt — "not paired … run
|
|
654
|
-
// `notifai init`" — which contradicts the correct line printed moments
|
|
655
|
-
// earlier and sends the reader back into the command that just failed.
|
|
656
|
-
let loginBlocker = null;
|
|
657
|
-
const attempted = new Set();
|
|
658
|
-
// Re-assess after every successful action. This is how a browser approval or
|
|
659
|
-
// companion registration can unlock the next state while the user is still
|
|
660
|
-
// here, without copying the dependency graph into a second setup script.
|
|
661
|
-
for (;;) {
|
|
662
|
-
let advanced = false;
|
|
663
|
-
let stop = false;
|
|
664
|
-
for (const state of readiness.states) {
|
|
665
|
-
if (state.status === 'ready')
|
|
666
|
-
continue;
|
|
667
|
-
// Question Routing is optional automation, so it is reported and never
|
|
668
|
-
// waited on. `doctor` still fails on a broken route; `init` walks past
|
|
669
|
-
// it, because a hook diagnostic standing in front of the delivery proof
|
|
670
|
-
// means a setup that can already send never proves that it can.
|
|
671
|
-
const optional = isOptionalSetup(state.id);
|
|
672
|
-
const halt = () => {
|
|
673
|
-
if (!optional)
|
|
674
|
-
stop = true;
|
|
675
|
-
return !optional;
|
|
676
|
-
};
|
|
677
|
-
if (state.status === 'unknown') {
|
|
678
|
-
if (halt())
|
|
679
|
-
break;
|
|
680
|
-
continue;
|
|
681
|
-
}
|
|
682
|
-
const remedy = state.remedy;
|
|
683
|
-
if (remedy === undefined || attempted.has(state.id)) {
|
|
684
|
-
if (state.status === 'gap' && halt())
|
|
685
|
-
break;
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
if (state.status === 'optional-gap') {
|
|
689
|
-
if (remedy.by !== 'cli' || !(await wantsOptional(workingDeps, state, resolved)))
|
|
690
|
-
continue;
|
|
691
|
-
if (needsSkillScope(state)) {
|
|
692
|
-
const scope = await scopeForSkillInstall(workingDeps, resolved);
|
|
693
|
-
if (scope === 'declined') {
|
|
694
|
-
attempted.add(state.id);
|
|
695
|
-
continue;
|
|
696
|
-
}
|
|
697
|
-
resolved = { ...resolved, skillsScope: scope };
|
|
698
|
-
}
|
|
699
|
-
attempted.add(state.id);
|
|
700
|
-
const result = await closeGap(workingDeps, state, resolved);
|
|
701
|
-
if (result === 'failed')
|
|
702
|
-
failed = true;
|
|
703
|
-
if (result === 'failed' && state.status === 'optional-gap') {
|
|
704
|
-
readiness = await reassess(refreshAfterClose(state.id));
|
|
705
|
-
advanced = true;
|
|
706
|
-
break;
|
|
707
|
-
}
|
|
708
|
-
if (result !== 'closed') {
|
|
709
|
-
if (halt())
|
|
710
|
-
break;
|
|
711
|
-
continue;
|
|
712
|
-
}
|
|
713
|
-
readiness = await reassess(refreshAfterClose(state.id));
|
|
714
|
-
advanced = true;
|
|
715
|
-
break;
|
|
716
|
-
}
|
|
717
|
-
if (remedy.by === 'cli') {
|
|
718
|
-
if (needsSkillScope(state)) {
|
|
719
|
-
const scope = await scopeForSkillInstall(workingDeps, resolved);
|
|
720
|
-
if (scope === 'declined') {
|
|
721
|
-
attempted.add(state.id);
|
|
722
|
-
if (halt())
|
|
723
|
-
break;
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
resolved = { ...resolved, skillsScope: scope };
|
|
727
|
-
}
|
|
728
|
-
attempted.add(state.id);
|
|
729
|
-
const result = await closeGap(workingDeps, state, resolved);
|
|
730
|
-
if (result === 'failed')
|
|
731
|
-
failed = true;
|
|
732
|
-
if (result === 'failed' && state.id === 'skill') {
|
|
733
|
-
readiness = await reassess(refreshAfterClose(state.id));
|
|
734
|
-
advanced = true;
|
|
735
|
-
break;
|
|
736
|
-
}
|
|
737
|
-
if (result !== 'closed') {
|
|
738
|
-
if (halt())
|
|
739
|
-
break;
|
|
740
|
-
continue;
|
|
741
|
-
}
|
|
742
|
-
readiness = await reassess(refreshAfterClose(state.id));
|
|
743
|
-
advanced = true;
|
|
744
|
-
break;
|
|
745
|
-
}
|
|
746
|
-
// The CLI starts approval; the User completes it in their browser.
|
|
747
|
-
// Both human and unattended init use the same bounded approval flow.
|
|
748
|
-
if ((state.id === 'credential' || state.id === 'auth') &&
|
|
749
|
-
remedy.by === 'user-here' &&
|
|
750
|
-
remedy.interactive === true) {
|
|
751
|
-
attempted.add(state.id);
|
|
752
|
-
workingDeps.io.out(workingDeps.io.interactive === true
|
|
753
|
-
? 'Connect this computer — Ctrl-C to stop.'
|
|
754
|
-
: 'Starting computer approval.');
|
|
755
|
-
const loginResult = await loginCommand(workingDeps, { ...(resolved.approval ? { approval: resolved.approval } : {}), ...(resolved.approvalEmail ? { approvalEmail: resolved.approvalEmail } : {}) }, (blocker) => {
|
|
756
|
-
loginBlocker = blocker;
|
|
757
|
-
// Both renderers must report what actually stopped approval: the
|
|
758
|
-
// access errand when the Account has none, or the approval still
|
|
759
|
-
// waiting in the browser — never the missing credential observed
|
|
760
|
-
// before the attempt began.
|
|
761
|
-
readiness = {
|
|
762
|
-
states: readiness.states.map((observed) => observed.id === blocker.id
|
|
763
|
-
? blocker
|
|
764
|
-
: blocker.id === 'auth' && observed.id === 'credential'
|
|
765
|
-
? { id: 'credential', title: 'This machine', status: 'unknown', detail: 'approval stopped before a machine credential was stored' }
|
|
766
|
-
: observed),
|
|
767
|
-
};
|
|
768
|
-
});
|
|
769
|
-
if (loginResult !== EXIT.ok) {
|
|
770
|
-
failed = true;
|
|
771
|
-
stop = true;
|
|
772
|
-
break;
|
|
773
|
-
}
|
|
774
|
-
readiness = await reassess();
|
|
775
|
-
advanced = true;
|
|
776
|
-
break;
|
|
777
|
-
}
|
|
778
|
-
if (state.id === 'devices' &&
|
|
779
|
-
remedy.by === 'user-elsewhere' &&
|
|
780
|
-
workingDeps.io.interactive === true) {
|
|
781
|
-
attempted.add(state.id);
|
|
782
|
-
const result = await waitForReadyDevice(workingDeps, state);
|
|
783
|
-
if (result === 'failed')
|
|
784
|
-
failed = true;
|
|
785
|
-
if (result !== 'closed') {
|
|
786
|
-
stop = true;
|
|
787
|
-
break;
|
|
788
|
-
}
|
|
789
|
-
readiness = await reassess();
|
|
790
|
-
advanced = true;
|
|
791
|
-
break;
|
|
792
|
-
}
|
|
793
|
-
// A human-only remedy is the first blocker for an unattended agent.
|
|
794
|
-
if (halt())
|
|
795
|
-
break;
|
|
796
|
-
continue;
|
|
797
|
-
}
|
|
798
|
-
if (stop || !advanced)
|
|
799
|
-
break;
|
|
800
|
-
}
|
|
801
|
-
const blocker = loginBlocker ?? firstRequiredBlocker(readiness);
|
|
802
|
-
if (flags.json === true)
|
|
803
|
-
deps.io.out(JSON.stringify(readinessJson(readiness), null, 2));
|
|
804
|
-
else
|
|
805
|
-
await printInitClose(deps, readiness, resolved, loginBlocker);
|
|
806
|
-
if (blocker === null)
|
|
807
|
-
return failed ? EXIT.failed : EXIT.ok;
|
|
808
|
-
return failed || workingDeps.io.interactive !== true ? EXIT.failed : EXIT.ok;
|
|
809
|
-
}
|
|
810
|
-
function leftoverOptionals(readiness, flags) {
|
|
811
|
-
return readiness.states.filter((state) => {
|
|
812
|
-
if (state.status !== 'optional-gap')
|
|
813
|
-
return false;
|
|
814
|
-
if (isOptionalAutomation(state.id))
|
|
815
|
-
return false;
|
|
816
|
-
// Not an install anyone declined, so "Optional, not set up" would misread
|
|
817
|
-
// it; it gets its own line below.
|
|
818
|
-
if (state.id === 'cli-bin')
|
|
819
|
-
return false;
|
|
820
|
-
if (state.id === 'skill' && flags.skills === false)
|
|
821
|
-
return false;
|
|
822
|
-
return true;
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* A `notifai` the reader cannot type.
|
|
827
|
-
*
|
|
828
|
-
* Said on the way out of every run, successful or not, because every next step
|
|
829
|
-
* this command prints — here, in the skill, in the README — names a command
|
|
830
|
-
* that will not be found until it is fixed.
|
|
831
|
-
*/
|
|
832
|
-
function printMissingCliBin(deps, readiness) {
|
|
833
|
-
const state = readiness.states.find((candidate) => candidate.id === 'cli-bin');
|
|
834
|
-
if (state?.status !== 'optional-gap')
|
|
835
|
-
return;
|
|
836
|
-
deps.io.out(`Heads up: ${state.detail}. ${remedyLine(state)}`);
|
|
837
|
-
}
|
|
838
|
-
function printOptionalLeftovers(deps, leftovers) {
|
|
839
|
-
for (const state of leftovers) {
|
|
840
|
-
deps.io.out(`Optional, not set up — ${remedyLine(state)}`);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
async function printInitClose(deps, readiness, flags, stoppedBy = null) {
|
|
844
|
-
const blocker = stoppedBy ?? firstRequiredBlocker(readiness);
|
|
845
|
-
const canSend = readiness.states.find((state) => state.id === 'devices')?.status === 'ready';
|
|
846
|
-
const questions = questionRoutingReady(readiness);
|
|
847
|
-
const leftovers = leftoverOptionals(readiness, flags).filter((state) => state.id !== 'contract');
|
|
848
|
-
if (blocker?.id === 'contract') {
|
|
849
|
-
deps.io.out(blocker.detail);
|
|
850
|
-
deps.io.out(blocker.remedy?.by !== 'user-elsewhere' && blocker.remedy?.command !== undefined
|
|
851
|
-
? blocker.remedy.command
|
|
852
|
-
: updateCliCommand(deps));
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
printMissingCliBin(deps, readiness);
|
|
856
|
-
if (deps.io.interactive === true) {
|
|
857
|
-
if (blocker === null) {
|
|
858
|
-
const lines = [
|
|
859
|
-
canSend ? 'You can send notifications.' : 'You are signed in.',
|
|
860
|
-
questions
|
|
861
|
-
? 'Questions will reach your devices.'
|
|
862
|
-
: 'Questions are terminal-only until Question Routing is ready.',
|
|
863
|
-
];
|
|
864
|
-
await deps.io.note?.(lines.join('\n'), 'Ready');
|
|
865
|
-
printOptionalLeftovers(deps, leftovers);
|
|
866
|
-
deps.io.out(questions
|
|
867
|
-
? 'All set. Agents in this project can notify you and ask you questions.'
|
|
868
|
-
: 'All set. Agents in this project can notify you. Questions are terminal-only until Question Routing is ready.');
|
|
869
|
-
await deps.io.outro?.('All set ✨');
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
// No framed copy of what the next three lines already say. The "Ready"
|
|
873
|
-
// note above adds something the close does not; this one only made a
|
|
874
|
-
// stopped visit state its one step twice, box then text.
|
|
875
|
-
deps.io.out(`Next: ${blocker.title} — ${blocker.detail}`);
|
|
876
|
-
deps.io.out(` ${remedyLine(blocker)}`);
|
|
877
|
-
if (shouldRestateInit(blocker)) {
|
|
878
|
-
deps.io.out(` Then re-run \`${SETUP_COMMAND}\` and it will pick up from here.`);
|
|
879
|
-
}
|
|
880
|
-
await deps.io.outro?.('One step remains (above)');
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
if (blocker === null) {
|
|
884
|
-
printOptionalLeftovers(deps, leftovers);
|
|
885
|
-
deps.io.out(questions
|
|
886
|
-
? 'All set. Agents in this project can notify you and ask you questions.'
|
|
887
|
-
: 'All set. Agents in this project can notify you. Questions are terminal-only until Question Routing is ready.');
|
|
888
|
-
return;
|
|
889
|
-
}
|
|
890
|
-
deps.io.out(`Next: ${blocker.title} — ${blocker.detail}`);
|
|
891
|
-
deps.io.out(` ${remedyLine(blocker)}`);
|
|
892
|
-
if (shouldRestateInit(blocker)) {
|
|
893
|
-
deps.io.out(` Then re-run \`${SETUP_COMMAND}\` and it will pick up from here.`);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
function shouldRestateInit(blocker) {
|
|
897
|
-
if (blocker.remedy?.by !== 'user-elsewhere' && blocker.remedy?.by !== 'user-here')
|
|
898
|
-
return false;
|
|
899
|
-
return !('command' in blocker.remedy) || blocker.remedy.command !== SETUP_COMMAND;
|
|
900
|
-
}
|
|
901
|
-
//# sourceMappingURL=commands-init.js.map
|