typeclaw 0.36.8 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +3 -2
- package/src/agent/index.ts +31 -11
- package/src/agent/live-sessions.ts +12 -0
- package/src/agent/model-fallback.ts +17 -15
- package/src/agent/model-overrides.ts +2 -2
- package/src/agent/session-meta.ts +10 -0
- package/src/agent/subagents.ts +11 -2
- package/src/agent/system-prompt.ts +9 -3
- package/src/agent/todo/continuation-policy.ts +6 -3
- package/src/agent/todo/continuation-wiring.ts +4 -2
- package/src/agent/todo/continuation.ts +3 -3
- package/src/agent/tools/todo/index.ts +27 -4
- package/src/bundled-plugins/agent-browser/index.ts +33 -108
- package/src/bundled-plugins/agent-browser/shim.ts +3 -94
- package/src/bundled-plugins/agent-browser/skills/agent-browser/SKILL.md +8 -33
- package/src/bundled-plugins/doc-render/skills/typeclaw-render-pdf/SKILL.md +2 -2
- package/src/bundled-plugins/guard/policies/memory-retrieval-cache-write.ts +7 -1
- package/src/bundled-plugins/memory/README.md +80 -23
- package/src/bundled-plugins/memory/append-tool.ts +74 -53
- package/src/bundled-plugins/memory/citation-superset.ts +4 -0
- package/src/bundled-plugins/memory/citations.ts +54 -0
- package/src/bundled-plugins/memory/dreaming-metrics.ts +30 -0
- package/src/bundled-plugins/memory/dreaming.ts +444 -21
- package/src/bundled-plugins/memory/index.ts +544 -400
- package/src/bundled-plugins/memory/load-memory.ts +87 -10
- package/src/bundled-plugins/memory/load-shards.ts +48 -22
- package/src/bundled-plugins/memory/memory-logger.ts +95 -106
- package/src/bundled-plugins/memory/memory-retrieval.ts +3 -3
- package/src/bundled-plugins/memory/parent-link.ts +33 -0
- package/src/bundled-plugins/memory/paths.ts +12 -0
- package/src/bundled-plugins/memory/references/frontmatter.ts +197 -0
- package/src/bundled-plugins/memory/references/load-references.ts +212 -0
- package/src/bundled-plugins/memory/references/store-reference-tool.ts +59 -0
- package/src/bundled-plugins/memory/search-tool.ts +282 -45
- package/src/bundled-plugins/memory/stream-events.ts +1 -0
- package/src/bundled-plugins/memory/stream-io.ts +28 -3
- package/src/bundled-plugins/memory/turn-dedup.ts +40 -0
- package/src/bundled-plugins/memory/vector/cache-write.ts +19 -0
- package/src/bundled-plugins/memory/vector/config.ts +28 -0
- package/src/bundled-plugins/memory/vector/doctor.ts +124 -0
- package/src/bundled-plugins/memory/vector/embedder.ts +246 -0
- package/src/bundled-plugins/memory/vector/hybrid.ts +439 -0
- package/src/bundled-plugins/memory/vector/index-on-write.ts +34 -0
- package/src/bundled-plugins/memory/vector/inspect.ts +111 -0
- package/src/bundled-plugins/memory/vector/passages.ts +125 -0
- package/src/bundled-plugins/memory/vector/reference-index-on-write.ts +50 -0
- package/src/bundled-plugins/memory/vector/relevance-gate.ts +93 -0
- package/src/bundled-plugins/memory/vector/startup.ts +71 -0
- package/src/bundled-plugins/memory/vector/store.ts +203 -0
- package/src/bundled-plugins/memory/vector/truncation.ts +124 -0
- package/src/bundled-plugins/security/policies/outbound-secret-scan.ts +2 -0
- package/src/channels/router.ts +239 -40
- package/src/cli/incomplete-init.ts +57 -0
- package/src/cli/init.ts +143 -12
- package/src/cli/inspect.ts +11 -5
- package/src/cli/model.ts +112 -34
- package/src/cli/restart.ts +24 -0
- package/src/cli/start.ts +24 -0
- package/src/cli/tunnel.ts +53 -8
- package/src/config/config.ts +110 -19
- package/src/config/index.ts +5 -1
- package/src/config/models-mutation.ts +29 -11
- package/src/config/providers-mutation.ts +2 -2
- package/src/config/providers.ts +146 -12
- package/src/container/shared.ts +9 -0
- package/src/container/start.ts +87 -4
- package/src/cron/consumer.ts +13 -7
- package/src/hostd/models.ts +64 -0
- package/src/hostd/paths.ts +6 -0
- package/src/hostd/portbroker-manager.ts +2 -2
- package/src/init/checkpoint.ts +201 -0
- package/src/init/dockerfile.ts +121 -34
- package/src/init/gitignore.ts +7 -7
- package/src/init/index.ts +41 -9
- package/src/init/models-dev.ts +96 -21
- package/src/init/oauth-login.ts +3 -3
- package/src/init/progress.ts +29 -0
- package/src/init/validate-api-key.ts +4 -0
- package/src/inspect/index.ts +13 -6
- package/src/inspect/item-list.ts +11 -2
- package/src/inspect/live-list.ts +65 -0
- package/src/inspect/open-item.ts +22 -1
- package/src/inspect/session-list.ts +29 -0
- package/src/models/embedding-model.ts +114 -0
- package/src/models/transformers-version.ts +55 -0
- package/src/plugin/types.ts +3 -0
- package/src/portbroker/container-server.ts +23 -0
- package/src/portbroker/forward-request-bus.ts +35 -0
- package/src/portbroker/forward-result-bus.ts +2 -3
- package/src/portbroker/hostd-client.ts +182 -36
- package/src/portbroker/index.ts +6 -1
- package/src/portbroker/protocol.ts +9 -2
- package/src/run/channel-session-factory.ts +11 -1
- package/src/run/index.ts +41 -7
- package/src/server/command-runner.ts +24 -1
- package/src/server/index.ts +42 -8
- package/src/shared/index.ts +2 -0
- package/src/shared/protocol.ts +31 -0
- package/src/skills/typeclaw-channels/SKILL.md +4 -4
- package/src/skills/typeclaw-config/SKILL.md +2 -2
- package/src/skills/typeclaw-memory/SKILL.md +3 -1
- package/src/skills/typeclaw-permissions/SKILL.md +3 -3
- package/src/skills/typeclaw-skills/SKILL.md +1 -1
- package/src/skills/typeclaw-tunnels/SKILL.md +22 -1
- package/src/tunnels/providers/cloudflare-quick.ts +65 -7
- package/src/tunnels/upstream-probe.ts +25 -0
- package/typeclaw.schema.json +156 -67
- package/src/bundled-plugins/agent-browser/dashboard-discovery.ts +0 -170
- package/src/bundled-plugins/agent-browser/dashboard-proxy.ts +0 -421
- package/src/portbroker/bind-with-forward.ts +0 -102
package/src/cli/init.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { defineCommand } from 'citty'
|
|
|
6
6
|
import {
|
|
7
7
|
KNOWN_PROVIDER_VENDORS,
|
|
8
8
|
KNOWN_PROVIDERS,
|
|
9
|
+
isKnownModelRef,
|
|
9
10
|
listKnownProviderVendorIds,
|
|
10
11
|
providerIdsForVendor,
|
|
11
12
|
supportsApiKey as providerSupportsApiKey,
|
|
@@ -35,9 +36,17 @@ import {
|
|
|
35
36
|
type KakaotalkAuthResult,
|
|
36
37
|
type LLMAuth,
|
|
37
38
|
} from '@/init'
|
|
39
|
+
import {
|
|
40
|
+
checkpointFromSelections,
|
|
41
|
+
createLocalWizardCheckpointStore,
|
|
42
|
+
sanitizeCheckpointAgainstCatalog,
|
|
43
|
+
type WizardAnswerCheckpointV1,
|
|
44
|
+
type WizardCheckpointStore,
|
|
45
|
+
} from '@/init/checkpoint'
|
|
38
46
|
import { runKakaotalkBootstrap } from '@/init/kakaotalk-auth'
|
|
39
|
-
import { fetchModelOptions, type ModelOption } from '@/init/models-dev'
|
|
47
|
+
import { customModelMetaFromOption, fetchModelOptions, type ModelOption } from '@/init/models-dev'
|
|
40
48
|
import { makeOAuthLoginRunner, type OAuthLoginResult } from '@/init/oauth-login'
|
|
49
|
+
import { detectInitProgress } from '@/init/progress'
|
|
41
50
|
import {
|
|
42
51
|
API_KEY_DASHBOARD_URL,
|
|
43
52
|
MINIMAX_TOKEN_PLAN_DASHBOARD_URL,
|
|
@@ -159,9 +168,10 @@ export const init = defineCommand({
|
|
|
159
168
|
}
|
|
160
169
|
preflightSpinner.stop('Docker is reachable.')
|
|
161
170
|
|
|
171
|
+
const checkpointStore = createLocalWizardCheckpointStore()
|
|
162
172
|
let collected: CollectedInputs
|
|
163
173
|
try {
|
|
164
|
-
collected = await collectWizardInputs(cwd, defaultWizardPrompts, { reset })
|
|
174
|
+
collected = await collectWizardInputs(cwd, defaultWizardPrompts, { reset, checkpointStore })
|
|
165
175
|
} catch (error) {
|
|
166
176
|
if (error instanceof WizardAbortedError) {
|
|
167
177
|
if (error.oauthCredentialsSaved) {
|
|
@@ -189,6 +199,8 @@ export const init = defineCommand({
|
|
|
189
199
|
kakaotalkPassword,
|
|
190
200
|
github: githubCredentials,
|
|
191
201
|
} = channelSecrets
|
|
202
|
+
const modelMeta = customModelMetaFromOption(model)
|
|
203
|
+
const visionModelMeta = vision !== undefined ? customModelMetaFromOption(vision.model) : undefined
|
|
192
204
|
|
|
193
205
|
// TODO: add remaining wizard steps from TypeClaw.md once their runtime lands:
|
|
194
206
|
// - git backup (url + PAT) — Phase 10
|
|
@@ -213,7 +225,14 @@ export const init = defineCommand({
|
|
|
213
225
|
cwd,
|
|
214
226
|
llmAuth,
|
|
215
227
|
model: model.ref,
|
|
216
|
-
...(
|
|
228
|
+
...(modelMeta !== undefined ? { modelMeta } : {}),
|
|
229
|
+
...(vision !== undefined
|
|
230
|
+
? {
|
|
231
|
+
visionModel: vision.model.ref,
|
|
232
|
+
...(visionModelMeta !== undefined ? { visionModelMeta } : {}),
|
|
233
|
+
visionAuth: vision.llmAuth,
|
|
234
|
+
}
|
|
235
|
+
: {}),
|
|
217
236
|
cliEntry: process.argv[1],
|
|
218
237
|
...(discordBotToken !== undefined ? { discordBotToken } : {}),
|
|
219
238
|
...(slackBotToken !== undefined ? { slackBotToken, slackAppToken } : {}),
|
|
@@ -271,6 +290,10 @@ export const init = defineCommand({
|
|
|
271
290
|
}
|
|
272
291
|
|
|
273
292
|
if (hatchingOk) {
|
|
293
|
+
// Clear the resume checkpoint only on full success (hatching ok). A
|
|
294
|
+
// failed install/dockerfile/git/hatching leaves it in place so the next
|
|
295
|
+
// `typeclaw init` — or `typeclaw start` (PR2) — can resume.
|
|
296
|
+
await checkpointStore.clear(cwd).catch(() => {})
|
|
274
297
|
const claimableChannel =
|
|
275
298
|
channelChoice !== 'none' && channelChoice !== 'github' ? channelDisplayName(channelChoice) : null
|
|
276
299
|
const hints: Array<{ label: string; command: string }> = []
|
|
@@ -380,7 +403,7 @@ export interface WizardPrompts {
|
|
|
380
403
|
pickModel: (
|
|
381
404
|
options: ModelOption[],
|
|
382
405
|
providerId: KnownProviderId,
|
|
383
|
-
initial:
|
|
406
|
+
initial: string | undefined,
|
|
384
407
|
) => Promise<StepResult<ModelOption>>
|
|
385
408
|
pickAuthMethod: (
|
|
386
409
|
provider: (typeof KNOWN_PROVIDERS)[KnownProviderId],
|
|
@@ -400,7 +423,7 @@ export interface WizardPrompts {
|
|
|
400
423
|
pickVisionModel: (
|
|
401
424
|
options: ModelOption[],
|
|
402
425
|
providerId: KnownProviderId,
|
|
403
|
-
initial:
|
|
426
|
+
initial: string | undefined,
|
|
404
427
|
) => Promise<StepResult<ModelOption>>
|
|
405
428
|
pickChannel: (initial: ChannelChoice | undefined) => Promise<StepResult<ChannelChoice>>
|
|
406
429
|
hasExistingChannelSecrets: (cwd: string, channel: Exclude<ChannelChoice, 'none'>) => Promise<boolean>
|
|
@@ -408,17 +431,19 @@ export interface WizardPrompts {
|
|
|
408
431
|
runOAuthLogin: (
|
|
409
432
|
provider: (typeof KNOWN_PROVIDERS)[KnownProviderId],
|
|
410
433
|
cwd: string,
|
|
411
|
-
model:
|
|
434
|
+
model: string,
|
|
412
435
|
) => Promise<OAuthLoginResult>
|
|
413
436
|
askOAuthFailureRecovery: (
|
|
414
437
|
provider: (typeof KNOWN_PROVIDERS)[KnownProviderId],
|
|
415
438
|
reason: string,
|
|
416
439
|
apiKeyAvailable: boolean,
|
|
417
440
|
) => Promise<OAuthFailureRecovery>
|
|
441
|
+
confirmResumeCheckpoint: (checkpoint: WizardAnswerCheckpointV1) => Promise<'resume' | 'start-over'>
|
|
418
442
|
}
|
|
419
443
|
|
|
420
444
|
export type CollectWizardInputsOptions = {
|
|
421
445
|
reset?: boolean
|
|
446
|
+
checkpointStore?: WizardCheckpointStore
|
|
422
447
|
}
|
|
423
448
|
|
|
424
449
|
export type OAuthFailureRecovery = 'retry' | 'api-key' | 'abort'
|
|
@@ -448,6 +473,7 @@ export const defaultWizardPrompts: WizardPrompts = {
|
|
|
448
473
|
}
|
|
449
474
|
},
|
|
450
475
|
askOAuthFailureRecovery,
|
|
476
|
+
confirmResumeCheckpoint,
|
|
451
477
|
}
|
|
452
478
|
|
|
453
479
|
export async function collectWizardInputs(
|
|
@@ -456,12 +482,46 @@ export async function collectWizardInputs(
|
|
|
456
482
|
options: CollectWizardInputsOptions = {},
|
|
457
483
|
): Promise<CollectedInputs> {
|
|
458
484
|
const reset = options.reset === true
|
|
485
|
+
const checkpointStore = options.checkpointStore
|
|
459
486
|
const catalog = await prompts.loadCatalog()
|
|
460
487
|
const state: WizardState = { catalog }
|
|
461
488
|
let step: StepId = 'pick-vendor'
|
|
462
489
|
let pendingBackOrigin: StepId | null = null
|
|
463
490
|
let oauthCredentialsSaved = false
|
|
464
491
|
|
|
492
|
+
// Resume saved wizard answers from a prior unfinished init. `--reset` skips
|
|
493
|
+
// this entirely (the user asked to re-answer everything). Route through the
|
|
494
|
+
// shared detectInitProgress() predicate so init/start/restart classify a
|
|
495
|
+
// checkpoint identically: only an `incomplete` init (checkpoint + not
|
|
496
|
+
// hatched) offers resume; a `complete-stale-checkpoint` (checkpoint that
|
|
497
|
+
// outlived a hatched agent) is cleared and ignored, matching the contract.
|
|
498
|
+
if (!reset && checkpointStore !== undefined) {
|
|
499
|
+
const progress = await detectInitProgress({ cwd, checkpointStore })
|
|
500
|
+
if (progress.kind === 'complete-stale-checkpoint') {
|
|
501
|
+
await checkpointStore.clear(cwd).catch(() => {})
|
|
502
|
+
} else if (progress.kind === 'incomplete') {
|
|
503
|
+
// Sanitize against the freshly-loaded catalog BEFORE showing the resume
|
|
504
|
+
// prompt. `load` only validates version/cwd/updatedAt, so a checkpoint
|
|
505
|
+
// with a since-removed provider/model id reaches here intact; describing
|
|
506
|
+
// or seeding it raw would crash on `KNOWN_PROVIDERS[providerId]`. Pruning
|
|
507
|
+
// first means the prompt, the seed, and any partial answers all see only
|
|
508
|
+
// catalog-valid fields.
|
|
509
|
+
const catalogRefs = new Set(catalog.options.map((option) => option.ref))
|
|
510
|
+
const sanitized = sanitizeCheckpointAgainstCatalog(progress.checkpoint, catalogRefs)
|
|
511
|
+
const decision = await prompts.confirmResumeCheckpoint(sanitized)
|
|
512
|
+
if (decision === 'resume') {
|
|
513
|
+
seedWizardState(state, sanitized)
|
|
514
|
+
} else {
|
|
515
|
+
await checkpointStore.clear(cwd)
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const persistCheckpoint = async (): Promise<void> => {
|
|
521
|
+
if (reset || checkpointStore === undefined) return
|
|
522
|
+
await checkpointStore.save(cwd, projectCheckpoint(cwd, state)).catch(() => {})
|
|
523
|
+
}
|
|
524
|
+
|
|
465
525
|
const abort = (): never => {
|
|
466
526
|
throw new WizardAbortedError({ oauthCredentialsSaved })
|
|
467
527
|
}
|
|
@@ -492,6 +552,11 @@ export async function collectWizardInputs(
|
|
|
492
552
|
}
|
|
493
553
|
|
|
494
554
|
while (true) {
|
|
555
|
+
// Persist the cumulative selections at the top of every iteration so a
|
|
556
|
+
// mid-wizard abort keeps everything answered so far. Running it here (not
|
|
557
|
+
// per-case) means back-navigation — which clears downstream fields before
|
|
558
|
+
// looping — overwrites the projection too, so stale answers never linger.
|
|
559
|
+
await persistCheckpoint()
|
|
495
560
|
switch (step) {
|
|
496
561
|
case 'pick-vendor': {
|
|
497
562
|
const result = onResult(step, await prompts.pickVendor(catalog.options, state.vendorId))
|
|
@@ -849,7 +914,7 @@ async function runOAuthLoginSafely(
|
|
|
849
914
|
prompts: WizardPrompts,
|
|
850
915
|
provider: (typeof KNOWN_PROVIDERS)[KnownProviderId],
|
|
851
916
|
cwd: string,
|
|
852
|
-
model:
|
|
917
|
+
model: string,
|
|
853
918
|
): Promise<OAuthLoginResult> {
|
|
854
919
|
try {
|
|
855
920
|
return await prompts.runOAuthLogin(provider, cwd, model)
|
|
@@ -871,6 +936,72 @@ function finalize(state: WizardState, channelSecrets: CollectedInputs['channelSe
|
|
|
871
936
|
}
|
|
872
937
|
}
|
|
873
938
|
|
|
939
|
+
async function confirmResumeCheckpoint(checkpoint: WizardAnswerCheckpointV1): Promise<'resume' | 'start-over'> {
|
|
940
|
+
const summary = describeCheckpoint(checkpoint)
|
|
941
|
+
const choice = await select({
|
|
942
|
+
message: 'Found answers from a previous, unfinished init. Resume from them?',
|
|
943
|
+
options: [
|
|
944
|
+
{ value: 'resume' as const, label: 'Resume', hint: summary },
|
|
945
|
+
{ value: 'start-over' as const, label: 'Start over', hint: 'discard saved answers and pick everything again' },
|
|
946
|
+
],
|
|
947
|
+
initialValue: 'resume' as const,
|
|
948
|
+
})
|
|
949
|
+
if (isCancel(choice)) return 'start-over'
|
|
950
|
+
return choice
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function describeCheckpoint(checkpoint: WizardAnswerCheckpointV1): string {
|
|
954
|
+
const parts: string[] = []
|
|
955
|
+
// Defense-in-depth: callers sanitize before describing, but a raw provider id
|
|
956
|
+
// missing from KNOWN_PROVIDERS must degrade to the id string, never throw.
|
|
957
|
+
if (checkpoint.modelRef !== undefined) parts.push(checkpoint.modelRef)
|
|
958
|
+
else if (checkpoint.providerId !== undefined) {
|
|
959
|
+
parts.push(KNOWN_PROVIDERS[checkpoint.providerId]?.name ?? checkpoint.providerId)
|
|
960
|
+
}
|
|
961
|
+
if (checkpoint.visionModelRef !== undefined) parts.push(`vision: ${checkpoint.visionModelRef}`)
|
|
962
|
+
if (checkpoint.channelChoice !== undefined && checkpoint.channelChoice !== 'none') {
|
|
963
|
+
parts.push(`channel: ${checkpoint.channelChoice}`)
|
|
964
|
+
}
|
|
965
|
+
return parts.length > 0 ? parts.join(', ') : 'partial selections'
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Pre-populate wizard state from a sanitized checkpoint so each prompt's
|
|
969
|
+
// `initial` argument fast-forwards the user to their prior choice. Only seeds
|
|
970
|
+
// fields whose upstream selections are all present and catalog-valid; the
|
|
971
|
+
// sanitize pass already dropped stale refs, so a missing field here just means
|
|
972
|
+
// that step prompts fresh.
|
|
973
|
+
function seedWizardState(state: WizardState, checkpoint: WizardAnswerCheckpointV1): void {
|
|
974
|
+
state.vendorId = checkpoint.vendorId
|
|
975
|
+
state.providerId = checkpoint.providerId
|
|
976
|
+
state.model = resolveModelOption(state.catalog, checkpoint.modelRef)
|
|
977
|
+
state.authMethod = checkpoint.authMethod
|
|
978
|
+
state.visionVendorId = checkpoint.visionVendorId
|
|
979
|
+
state.visionProviderId = checkpoint.visionProviderId
|
|
980
|
+
state.visionModel = resolveModelOption(state.catalog, checkpoint.visionModelRef)
|
|
981
|
+
state.visionAuthMethod = checkpoint.visionAuthMethod
|
|
982
|
+
state.channelChoice = checkpoint.channelChoice
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function resolveModelOption(catalog: WizardState['catalog'], ref: string | undefined): ModelOption | undefined {
|
|
986
|
+
if (catalog === undefined || ref === undefined) return undefined
|
|
987
|
+
return catalog.options.find((option) => option.ref === ref)
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function projectCheckpoint(cwd: string, state: WizardState): WizardAnswerCheckpointV1 {
|
|
991
|
+
return checkpointFromSelections({
|
|
992
|
+
cwd,
|
|
993
|
+
...(state.vendorId !== undefined ? { vendorId: state.vendorId } : {}),
|
|
994
|
+
...(state.providerId !== undefined ? { providerId: state.providerId } : {}),
|
|
995
|
+
...(state.model?.ref !== undefined ? { modelRef: state.model.ref } : {}),
|
|
996
|
+
...(state.authMethod !== undefined ? { authMethod: state.authMethod } : {}),
|
|
997
|
+
...(state.visionVendorId !== undefined ? { visionVendorId: state.visionVendorId } : {}),
|
|
998
|
+
...(state.visionProviderId !== undefined ? { visionProviderId: state.visionProviderId } : {}),
|
|
999
|
+
...(state.visionModel?.ref !== undefined ? { visionModelRef: state.visionModel.ref } : {}),
|
|
1000
|
+
...(state.visionAuthMethod !== undefined ? { visionAuthMethod: state.visionAuthMethod } : {}),
|
|
1001
|
+
...(state.channelChoice !== undefined ? { channelChoice: state.channelChoice } : {}),
|
|
1002
|
+
})
|
|
1003
|
+
}
|
|
1004
|
+
|
|
874
1005
|
function channelDisplayName(choice: Exclude<ChannelChoice, 'none'>): string {
|
|
875
1006
|
switch (choice) {
|
|
876
1007
|
case 'slack':
|
|
@@ -990,7 +1121,7 @@ async function pickProviderVariant(
|
|
|
990
1121
|
async function pickModelForProvider(
|
|
991
1122
|
options: ModelOption[],
|
|
992
1123
|
providerId: KnownProviderId,
|
|
993
|
-
initial:
|
|
1124
|
+
initial: string | undefined,
|
|
994
1125
|
): Promise<StepResult<ModelOption>> {
|
|
995
1126
|
const candidates = sortRecommendedFirst(options.filter((o) => o.providerId === providerId))
|
|
996
1127
|
// select<string>, not select<KnownModelRef>: clack's Option<Value> is a
|
|
@@ -1069,7 +1200,7 @@ async function pickVisionProviderVariant(
|
|
|
1069
1200
|
async function pickVisionModel(
|
|
1070
1201
|
options: ModelOption[],
|
|
1071
1202
|
providerId: KnownProviderId,
|
|
1072
|
-
initial:
|
|
1203
|
+
initial: string | undefined,
|
|
1073
1204
|
): Promise<StepResult<ModelOption>> {
|
|
1074
1205
|
const candidates = sortRecommendedFirst(options.filter((o) => o.providerId === providerId))
|
|
1075
1206
|
// select<string> for the same distributive-Option reason as pickModelForProvider.
|
|
@@ -1697,12 +1828,12 @@ const RECOMMENDED_MODEL_REFS: ReadonlySet<KnownModelRef> = new Set<KnownModelRef
|
|
|
1697
1828
|
])
|
|
1698
1829
|
|
|
1699
1830
|
export function formatModelLabel(o: ModelOption): string {
|
|
1700
|
-
return RECOMMENDED_MODEL_REFS.has(o.ref) ? `${o.modelName} (Recommended)` : o.modelName
|
|
1831
|
+
return isKnownModelRef(o.ref) && RECOMMENDED_MODEL_REFS.has(o.ref) ? `${o.modelName} (Recommended)` : o.modelName
|
|
1701
1832
|
}
|
|
1702
1833
|
|
|
1703
1834
|
export function sortRecommendedFirst(options: ModelOption[]): ModelOption[] {
|
|
1704
|
-
const recommended = options.filter((o) => RECOMMENDED_MODEL_REFS.has(o.ref))
|
|
1705
|
-
const rest = options.filter((o) => !RECOMMENDED_MODEL_REFS.has(o.ref))
|
|
1835
|
+
const recommended = options.filter((o) => isKnownModelRef(o.ref) && RECOMMENDED_MODEL_REFS.has(o.ref))
|
|
1836
|
+
const rest = options.filter((o) => !isKnownModelRef(o.ref) || !RECOMMENDED_MODEL_REFS.has(o.ref))
|
|
1706
1837
|
return [...recommended, ...rest]
|
|
1707
1838
|
}
|
|
1708
1839
|
|
package/src/cli/inspect.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { defineCommand } from 'citty'
|
|
|
3
3
|
import { requireContainerRunning, resolveHostPort, resolveTuiToken } from '@/container'
|
|
4
4
|
import { findAgentDir } from '@/init'
|
|
5
5
|
import {
|
|
6
|
+
fetchLiveSessions,
|
|
6
7
|
listViewerItems,
|
|
7
8
|
openViewerItem,
|
|
8
9
|
parseDuration,
|
|
@@ -150,7 +151,8 @@ export async function runInspectViewer(opts: RunInspectViewerOptions): Promise<n
|
|
|
150
151
|
|
|
151
152
|
const interactive = Boolean(process.stdin.isTTY)
|
|
152
153
|
const liveHint = interactive ? escHintLine(color) : undefined
|
|
153
|
-
const
|
|
154
|
+
const inspectUrl = containerRunning ? await resolveInspectUrl(cwd) : undefined
|
|
155
|
+
const liveSource = inspectUrl !== undefined ? buildLiveSource(inspectUrl) : undefined
|
|
154
156
|
|
|
155
157
|
const stdout = (line: string): void => {
|
|
156
158
|
process.stdout.write(`${line}\n`)
|
|
@@ -186,6 +188,7 @@ export async function runInspectViewer(opts: RunInspectViewerOptions): Promise<n
|
|
|
186
188
|
onWarn: stderr,
|
|
187
189
|
}
|
|
188
190
|
if (sinceMs !== undefined) listOpts.sinceMs = sinceMs
|
|
191
|
+
if (inspectUrl !== undefined) listOpts.liveSessions = await fetchLiveSessions({ url: inspectUrl })
|
|
189
192
|
return (await listViewerItems(listOpts)).items
|
|
190
193
|
},
|
|
191
194
|
keyOf: (item) => (item.kind === 'logs' ? 'logs' : item.summary.sessionId),
|
|
@@ -223,12 +226,15 @@ async function resolveTuiUrl(cwd: string): Promise<string> {
|
|
|
223
226
|
return url.toString()
|
|
224
227
|
}
|
|
225
228
|
|
|
226
|
-
async function
|
|
229
|
+
async function resolveInspectUrl(cwd: string): Promise<string> {
|
|
227
230
|
const port = await resolveHostPort({ cwd })
|
|
228
231
|
const token = await resolveTuiToken({ cwd })
|
|
229
232
|
const baseUrl = new URL(`ws://127.0.0.1:${port}/inspect`)
|
|
230
233
|
if (token !== null) baseUrl.searchParams.set('token', token)
|
|
231
|
-
|
|
234
|
+
return baseUrl.toString()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function buildLiveSource(url: string): LiveSourceFactory {
|
|
232
238
|
return ({ sessionId, sinceMs, signal, onSubscribed }) =>
|
|
233
239
|
streamLive({
|
|
234
240
|
url,
|
|
@@ -325,8 +331,8 @@ function itemHint(item: ViewerItem): { hint: string } {
|
|
|
325
331
|
function sessionRowLabel(s: SessionSummary): string {
|
|
326
332
|
const id = shortSessionId(s.sessionId)
|
|
327
333
|
const label = s.origin === null ? '(unknown origin)' : originLabel(s.origin)
|
|
328
|
-
const when = formatRelative(s.mtimeMs)
|
|
329
|
-
return `${c.cyan(id)} ${label} ${
|
|
334
|
+
const when = s.live === true ? c.green('live · replying') : c.dim(formatRelative(s.mtimeMs))
|
|
335
|
+
return `${c.cyan(id)} ${label} ${when}`
|
|
330
336
|
}
|
|
331
337
|
|
|
332
338
|
function formatRelative(ms: number): string {
|
package/src/cli/model.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cancel, intro, isCancel, log, select } from '@clack/prompts'
|
|
2
2
|
import { defineCommand } from 'citty'
|
|
3
3
|
|
|
4
|
+
import type { CustomModelMeta } from '@/config'
|
|
4
5
|
import {
|
|
5
6
|
addProfile,
|
|
6
7
|
listModelProfiles,
|
|
@@ -9,19 +10,25 @@ import {
|
|
|
9
10
|
setProfile,
|
|
10
11
|
} from '@/config/models-mutation'
|
|
11
12
|
import {
|
|
13
|
+
isKnownModelRef,
|
|
12
14
|
KNOWN_PROVIDERS,
|
|
13
|
-
listKnownModelRefs,
|
|
14
15
|
providerForModelRef,
|
|
15
16
|
type KnownModelRef,
|
|
16
17
|
type KnownProviderId,
|
|
17
18
|
} from '@/config/providers'
|
|
18
19
|
import { findAgentDir, isInitialized } from '@/init'
|
|
20
|
+
import { customModelMetaFromOption, fetchModelOptions, type ModelOption } from '@/init/models-dev'
|
|
19
21
|
|
|
20
22
|
import { runProviderAddFlow } from './provider'
|
|
21
23
|
import { c, done, errorLine } from './ui'
|
|
22
24
|
|
|
23
25
|
const ADD_PROVIDER_SENTINEL = '__add-provider__'
|
|
24
26
|
|
|
27
|
+
type PickedModelRef = {
|
|
28
|
+
ref: string
|
|
29
|
+
meta?: CustomModelMeta
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
const setSub = defineCommand({
|
|
26
33
|
meta: {
|
|
27
34
|
name: 'set',
|
|
@@ -47,18 +54,21 @@ const setSub = defineCommand({
|
|
|
47
54
|
async run({ args }) {
|
|
48
55
|
const cwd = ensureAgentDir()
|
|
49
56
|
const profile = args.profile ?? (await pickProfileName())
|
|
50
|
-
const
|
|
57
|
+
const picked = args.ref !== undefined ? await resolveExplicitRef(args.ref) : await pickModelRef(cwd)
|
|
51
58
|
|
|
52
|
-
intro(`Setting model profile: ${profile} → ${ref}`)
|
|
59
|
+
intro(`Setting model profile: ${profile} → ${picked.ref}`)
|
|
53
60
|
|
|
54
|
-
const result = setProfile(cwd, profile, ref, {
|
|
61
|
+
const result = setProfile(cwd, profile, picked.ref, {
|
|
62
|
+
force: args.force === true,
|
|
63
|
+
...(picked.meta !== undefined ? { meta: picked.meta } : {}),
|
|
64
|
+
})
|
|
55
65
|
if (!result.ok) {
|
|
56
66
|
console.error(errorLine(result.reason))
|
|
57
67
|
process.exit(1)
|
|
58
68
|
}
|
|
59
69
|
done({
|
|
60
70
|
title: c.green(`Profile "${profile}" set.`),
|
|
61
|
-
details: `${profile} → ${ref}`,
|
|
71
|
+
details: `${profile} → ${picked.ref}`,
|
|
62
72
|
hints: [{ label: 'If the agent is running:', command: 'typeclaw reload' }],
|
|
63
73
|
})
|
|
64
74
|
},
|
|
@@ -88,18 +98,21 @@ const addSub = defineCommand({
|
|
|
88
98
|
},
|
|
89
99
|
async run({ args }) {
|
|
90
100
|
const cwd = ensureAgentDir()
|
|
91
|
-
const
|
|
101
|
+
const picked = args.ref !== undefined ? await resolveExplicitRef(args.ref) : await pickModelRef(cwd)
|
|
92
102
|
|
|
93
|
-
intro(`Adding model profile: ${args.profile} → ${ref}`)
|
|
103
|
+
intro(`Adding model profile: ${args.profile} → ${picked.ref}`)
|
|
94
104
|
|
|
95
|
-
const result = addProfile(cwd, args.profile, ref, {
|
|
105
|
+
const result = addProfile(cwd, args.profile, picked.ref, {
|
|
106
|
+
force: args.force === true,
|
|
107
|
+
...(picked.meta !== undefined ? { meta: picked.meta } : {}),
|
|
108
|
+
})
|
|
96
109
|
if (!result.ok) {
|
|
97
110
|
console.error(errorLine(result.reason))
|
|
98
111
|
process.exit(1)
|
|
99
112
|
}
|
|
100
113
|
done({
|
|
101
114
|
title: c.green(`Profile "${args.profile}" added.`),
|
|
102
|
-
details: `${args.profile} → ${ref}`,
|
|
115
|
+
details: `${args.profile} → ${picked.ref}`,
|
|
103
116
|
hints: [{ label: 'If the agent is running:', command: 'typeclaw reload' }],
|
|
104
117
|
})
|
|
105
118
|
},
|
|
@@ -146,7 +159,7 @@ const listSub = defineCommand({
|
|
|
146
159
|
},
|
|
147
160
|
async run({ args }) {
|
|
148
161
|
if (args.available === true) {
|
|
149
|
-
printAvailableRefs()
|
|
162
|
+
await printAvailableRefs()
|
|
150
163
|
return
|
|
151
164
|
}
|
|
152
165
|
const cwd = ensureAgentDir()
|
|
@@ -218,7 +231,7 @@ async function pickProfileName(): Promise<string> {
|
|
|
218
231
|
return choice
|
|
219
232
|
}
|
|
220
233
|
|
|
221
|
-
async function pickModelRef(cwd: string): Promise<
|
|
234
|
+
async function pickModelRef(cwd: string): Promise<PickedModelRef> {
|
|
222
235
|
while (true) {
|
|
223
236
|
const refs = listRegisteredModelRefs(cwd)
|
|
224
237
|
if (refs.length === 0) {
|
|
@@ -234,13 +247,14 @@ async function pickModelRef(cwd: string): Promise<string> {
|
|
|
234
247
|
// distributive conditional type and a large ref union breaks `value: ref`
|
|
235
248
|
// assignability. Values are ref strings (+ the sentinel) and stay correct
|
|
236
249
|
// at runtime — the sentinel check and `return choice` below are unaffected.
|
|
250
|
+
const modelOptions = await listCredentialedModelOptions(refs)
|
|
237
251
|
const choice = await select<string>({
|
|
238
252
|
message: 'Pick a model',
|
|
239
253
|
options: [
|
|
240
|
-
...
|
|
241
|
-
value: ref,
|
|
242
|
-
label: describeRef(ref),
|
|
243
|
-
hint: ref,
|
|
254
|
+
...modelOptions.map((option) => ({
|
|
255
|
+
value: option.ref,
|
|
256
|
+
label: describeRef(option.ref),
|
|
257
|
+
hint: option.ref,
|
|
244
258
|
})),
|
|
245
259
|
{
|
|
246
260
|
value: ADD_PROVIDER_SENTINEL,
|
|
@@ -248,13 +262,18 @@ async function pickModelRef(cwd: string): Promise<string> {
|
|
|
248
262
|
hint: 'configure a new provider',
|
|
249
263
|
},
|
|
250
264
|
],
|
|
251
|
-
initialValue: refs[0],
|
|
265
|
+
initialValue: modelOptions[0]?.ref ?? refs[0],
|
|
252
266
|
})
|
|
253
267
|
if (isCancel(choice)) {
|
|
254
268
|
cancel('Aborted.')
|
|
255
269
|
process.exit(0)
|
|
256
270
|
}
|
|
257
|
-
if (choice !== ADD_PROVIDER_SENTINEL)
|
|
271
|
+
if (choice !== ADD_PROVIDER_SENTINEL) {
|
|
272
|
+
const option = modelOptions.find((candidate) => candidate.ref === choice)
|
|
273
|
+
if (option === undefined) return { ref: choice }
|
|
274
|
+
const meta = customModelMetaFromOption(option)
|
|
275
|
+
return { ref: option.ref, ...(meta !== undefined ? { meta } : {}) }
|
|
276
|
+
}
|
|
258
277
|
const added = await runProviderAddFlow(cwd, {})
|
|
259
278
|
if (!added.ok) {
|
|
260
279
|
console.error(errorLine(added.reason))
|
|
@@ -263,29 +282,88 @@ async function pickModelRef(cwd: string): Promise<string> {
|
|
|
263
282
|
}
|
|
264
283
|
}
|
|
265
284
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
285
|
+
// Non-interactive `<ref>` path. Curated refs resolve from KNOWN_PROVIDERS, so
|
|
286
|
+
// they need no metadata. Non-curated refs are looked up in the live catalog so
|
|
287
|
+
// `customModels[ref]` carries the same metadata the interactive picker would
|
|
288
|
+
// persist; without it `resolveModel` silently falls back to defaults. A
|
|
289
|
+
// catalog miss (offline / unknown id) still writes the ref, but warns first.
|
|
290
|
+
export async function resolveExplicitRef(
|
|
291
|
+
ref: string,
|
|
292
|
+
loadCatalog: () => Promise<{ options: ModelOption[] }> = fetchModelOptions,
|
|
293
|
+
): Promise<PickedModelRef> {
|
|
294
|
+
if (isKnownModelRef(ref)) return { ref }
|
|
295
|
+
const { options } = await loadCatalog()
|
|
296
|
+
const option = options.find((candidate) => candidate.ref === ref)
|
|
297
|
+
if (option === undefined) {
|
|
298
|
+
log.warn(
|
|
299
|
+
`"${ref}" isn't in the live catalog; saving the ref without metadata. ` +
|
|
300
|
+
`The agent will use fallback defaults (reasoning off, text-only input, zero cost, provider-default context).`,
|
|
301
|
+
)
|
|
302
|
+
return { ref }
|
|
303
|
+
}
|
|
304
|
+
const meta = customModelMetaFromOption(option)
|
|
305
|
+
return { ref, ...(meta !== undefined ? { meta } : {}) }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export type { PickedModelRef }
|
|
309
|
+
|
|
310
|
+
async function listCredentialedModelOptions(refs: KnownModelRef[]): Promise<ModelOption[]> {
|
|
311
|
+
const credentialedProviders = new Set<KnownProviderId>(refs.map((ref) => providerForModelRef(ref)))
|
|
312
|
+
const catalog = await fetchModelOptions()
|
|
313
|
+
const options = catalog.options.filter((option) => credentialedProviders.has(option.providerId))
|
|
314
|
+
if (options.length > 0) return options
|
|
315
|
+
return refs.map((ref) => {
|
|
316
|
+
const providerId = providerForModelRef(ref)
|
|
317
|
+
const modelId = ref.slice(providerId.length + 1)
|
|
318
|
+
const model = (
|
|
319
|
+
KNOWN_PROVIDERS[providerId].models as Record<
|
|
320
|
+
string,
|
|
321
|
+
{ name: string; reasoning?: boolean; contextWindow?: number; input?: ReadonlyArray<string> }
|
|
322
|
+
>
|
|
323
|
+
)[modelId]
|
|
324
|
+
return {
|
|
325
|
+
ref,
|
|
326
|
+
providerId,
|
|
327
|
+
providerName: KNOWN_PROVIDERS[providerId].name,
|
|
328
|
+
modelId,
|
|
329
|
+
modelName: model?.name ?? modelId,
|
|
330
|
+
reasoning: model?.reasoning ?? false,
|
|
331
|
+
contextWindow: model?.contextWindow ?? null,
|
|
332
|
+
curated: true,
|
|
333
|
+
supportsVision: model?.input?.includes('image') ?? false,
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function describeRef(ref: string): string {
|
|
339
|
+
try {
|
|
340
|
+
const providerId = providerForModelRef(ref)
|
|
341
|
+
const modelId = ref.slice(providerId.length + 1)
|
|
342
|
+
const provider = KNOWN_PROVIDERS[providerId]
|
|
343
|
+
const model = (provider.models as Record<string, { name: string }>)[modelId]
|
|
344
|
+
return `${provider.name} · ${model?.name ?? modelId}`
|
|
345
|
+
} catch {
|
|
346
|
+
return ref
|
|
347
|
+
}
|
|
272
348
|
}
|
|
273
349
|
|
|
274
|
-
function printAvailableRefs(): void {
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
350
|
+
async function printAvailableRefs(): Promise<void> {
|
|
351
|
+
const { options, source, warning } = await fetchModelOptions()
|
|
352
|
+
if (options.length === 0) {
|
|
277
353
|
console.log(c.dim('No models registered.'))
|
|
278
354
|
return
|
|
279
355
|
}
|
|
280
356
|
console.log(c.dim('Use `typeclaw model set <profile> <ref>` to apply.'))
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
357
|
+
if (source === 'curated' && warning !== undefined) {
|
|
358
|
+
console.log(c.dim(`Using built-in catalog (models.dev unavailable: ${warning}).`))
|
|
359
|
+
}
|
|
360
|
+
for (const providerId of Object.keys(KNOWN_PROVIDERS) as KnownProviderId[]) {
|
|
361
|
+
const providerOptions = options.filter((option) => option.providerId === providerId)
|
|
362
|
+
if (providerOptions.length === 0) continue
|
|
363
|
+
console.log('')
|
|
364
|
+
console.log(c.cyan(KNOWN_PROVIDERS[providerId].name))
|
|
365
|
+
for (const option of providerOptions) {
|
|
366
|
+
console.log(` ${option.ref}`)
|
|
288
367
|
}
|
|
289
|
-
console.log(` ${ref}`)
|
|
290
368
|
}
|
|
291
369
|
}
|
package/src/cli/restart.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { confirm, isCancel } from '@clack/prompts'
|
|
1
2
|
import { defineCommand } from 'citty'
|
|
2
3
|
|
|
3
4
|
import { config, validateConfig } from '@/config'
|
|
4
5
|
import { start, stop } from '@/container'
|
|
5
6
|
import { findAgentDir, isInitialized } from '@/init'
|
|
6
7
|
|
|
8
|
+
import { guardIncompleteInit } from './incomplete-init'
|
|
7
9
|
import { c, errorLine, renderStartSuccess, spinner } from './ui'
|
|
8
10
|
|
|
9
11
|
export const restartCommand = defineCommand({
|
|
@@ -27,6 +29,28 @@ export const restartCommand = defineCommand({
|
|
|
27
29
|
async run({ args }) {
|
|
28
30
|
const cwd = findAgentDir(process.cwd()) ?? process.cwd()
|
|
29
31
|
|
|
32
|
+
// Runs before BOTH isInitialized and stop. A wizard abort persists a
|
|
33
|
+
// checkpoint before scaffold writes typeclaw.json, so a checkpoint-but-no-
|
|
34
|
+
// config dir is an incomplete init that should get resume guidance, not the
|
|
35
|
+
// generic config-missing error — and a half-init agent usually has no
|
|
36
|
+
// container to stop. A `continue` falls through to isInitialized, which
|
|
37
|
+
// still catches a truly uninitialized dir.
|
|
38
|
+
const guard = await guardIncompleteInit({
|
|
39
|
+
cwd,
|
|
40
|
+
interactive: Boolean(process.stdout.isTTY),
|
|
41
|
+
confirmContinue: async () => {
|
|
42
|
+
const proceed = await confirm({ message: 'Try restarting anyway?', initialValue: false })
|
|
43
|
+
return !isCancel(proceed) && proceed === true
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
if (guard.action === 'block') {
|
|
47
|
+
console.error(errorLine(guard.message))
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
if (guard.action === 'abort') {
|
|
51
|
+
process.exit(0)
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
if (!isInitialized(cwd)) {
|
|
31
55
|
console.error(errorLine('TypeClaw config file not found. Run `typeclaw init` first.'))
|
|
32
56
|
process.exit(1)
|
package/src/cli/start.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { confirm, isCancel } from '@clack/prompts'
|
|
1
2
|
import { defineCommand } from 'citty'
|
|
2
3
|
|
|
3
4
|
import { config, validateConfig } from '@/config'
|
|
4
5
|
import { start } from '@/container'
|
|
5
6
|
import { findAgentDir, isInitialized } from '@/init'
|
|
6
7
|
|
|
8
|
+
import { guardIncompleteInit } from './incomplete-init'
|
|
7
9
|
import { errorLine, renderStartSuccess, spinner } from './ui'
|
|
8
10
|
|
|
9
11
|
export const startCommand = defineCommand({
|
|
@@ -27,6 +29,28 @@ export const startCommand = defineCommand({
|
|
|
27
29
|
async run({ args }) {
|
|
28
30
|
const cwd = findAgentDir(process.cwd()) ?? process.cwd()
|
|
29
31
|
|
|
32
|
+
// Runs BEFORE the isInitialized check: a wizard abort persists a checkpoint
|
|
33
|
+
// before scaffold writes typeclaw.json, so a checkpoint-but-no-config dir is
|
|
34
|
+
// an incomplete init, not a "never initialized" one. Guarding first means
|
|
35
|
+
// that case gets the resume guidance instead of the generic config-missing
|
|
36
|
+
// error. A `continue` (no incomplete checkpoint, or "try anyway") falls
|
|
37
|
+
// through to isInitialized, which still catches a truly uninitialized dir.
|
|
38
|
+
const guard = await guardIncompleteInit({
|
|
39
|
+
cwd,
|
|
40
|
+
interactive: Boolean(process.stdout.isTTY),
|
|
41
|
+
confirmContinue: async () => {
|
|
42
|
+
const proceed = await confirm({ message: 'Try starting anyway?', initialValue: false })
|
|
43
|
+
return !isCancel(proceed) && proceed === true
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
if (guard.action === 'block') {
|
|
47
|
+
console.error(errorLine(guard.message))
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
if (guard.action === 'abort') {
|
|
51
|
+
process.exit(0)
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
if (!isInitialized(cwd)) {
|
|
31
55
|
console.error(errorLine('TypeClaw config file not found. Run `typeclaw init` first.'))
|
|
32
56
|
process.exit(1)
|