akm-cli 0.8.1 → 0.9.0-beta.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/CHANGELOG.md +258 -0
- package/dist/assets/help/help-proposals.md +1 -2
- package/dist/assets/hints/cli-hints-full.md +34 -19
- package/dist/assets/hints/cli-hints-short.md +1 -1
- package/dist/assets/profiles/catchup.json +13 -0
- package/dist/assets/profiles/consolidate.json +13 -0
- package/dist/assets/profiles/frequent.json +13 -0
- package/dist/assets/stash-skeleton/README.md +76 -0
- package/dist/assets/tasks/core/backup.yml +4 -0
- package/dist/assets/tasks/core/extract.yml +4 -0
- package/dist/assets/tasks/core/improve.yml +4 -0
- package/dist/assets/tasks/core/index-refresh.yml +4 -0
- package/dist/assets/tasks/core/sync.yml +4 -0
- package/dist/assets/tasks/core/update-stashes.yml +4 -0
- package/dist/assets/tasks/core/version-check.yml +4 -0
- package/dist/cli/config-migrate.js +6 -6
- package/dist/cli/config-validate.js +4 -4
- package/dist/cli/confirm.js +3 -3
- package/dist/cli/parse-args.js +1 -1
- package/dist/cli/shared.js +51 -14
- package/dist/cli-node.mjs +26 -0
- package/dist/cli.js +171 -3857
- package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
- package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
- package/dist/commands/agent/contribute-cli.js +200 -0
- package/dist/commands/completions.js +1 -1
- package/dist/commands/config-cli.js +240 -3
- package/dist/commands/config-edit.js +344 -0
- package/dist/commands/db-cli.js +2 -2
- package/dist/commands/env/env-cli.js +529 -0
- package/dist/commands/env/env.js +410 -0
- package/dist/commands/env/secret-cli.js +259 -0
- package/dist/commands/{secret.js → env/secret.js} +6 -47
- package/dist/commands/events.js +4 -4
- package/dist/commands/feedback-cli.js +18 -34
- package/dist/commands/graph/graph-cli.js +132 -0
- package/dist/commands/{graph.js → graph/graph.js} +22 -16
- package/dist/commands/health/checks.js +279 -0
- package/dist/commands/health.js +101 -249
- package/dist/commands/{consolidate.js → improve/consolidate.js} +52 -40
- package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
- package/dist/commands/{distill.js → improve/distill.js} +39 -18
- package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
- package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
- package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
- package/dist/commands/{extract.js → improve/extract.js} +185 -26
- package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +4 -4
- package/dist/commands/{improve-cli.js → improve/improve-cli.js} +45 -23
- package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
- package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +10 -5
- package/dist/commands/{improve.js → improve/improve.js} +536 -248
- package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
- package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
- package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
- package/dist/commands/{reflect.js → improve/reflect.js} +33 -28
- package/dist/commands/improve/session-asset.js +248 -0
- package/dist/commands/lint/agent-linter.js +1 -1
- package/dist/commands/lint/base-linter.js +55 -37
- package/dist/commands/lint/command-linter.js +1 -1
- package/dist/commands/lint/default-linter.js +1 -1
- package/dist/commands/lint/env-key-rules.js +1 -1
- package/dist/commands/lint/index.js +19 -25
- package/dist/commands/lint/knowledge-linter.js +1 -1
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +8 -8
- package/dist/commands/lint/skill-linter.js +1 -1
- package/dist/commands/lint/task-linter.js +1 -1
- package/dist/commands/lint/workflow-linter.js +1 -1
- package/dist/commands/lint.js +1 -1
- package/dist/commands/observability-cli.js +244 -0
- package/dist/commands/{proposal-drain-policies.js → proposal/drain-policies.js} +3 -3
- package/dist/commands/{proposal-drain.js → proposal/drain.js} +15 -10
- package/dist/commands/proposal/proposal-cli.js +478 -0
- package/dist/commands/{proposal.js → proposal/proposal.js} +5 -5
- package/dist/commands/{propose.js → proposal/propose.js} +11 -11
- package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
- package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
- package/dist/{core → commands/proposal/validators}/proposals.js +13 -7
- package/dist/commands/{curate.js → read/curate.js} +7 -7
- package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
- package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
- package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
- package/dist/commands/read/search-cli.js +207 -0
- package/dist/commands/{search.js → read/search.js} +22 -27
- package/dist/commands/{show.js → read/show.js} +77 -44
- package/dist/commands/registry-cli.js +8 -8
- package/dist/commands/remember.js +8 -8
- package/dist/commands/sources/add-cli.js +293 -0
- package/dist/commands/{history.js → sources/history.js} +27 -25
- package/dist/commands/{info.js → sources/info.js} +6 -6
- package/dist/commands/{init.js → sources/init.js} +10 -5
- package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
- package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
- package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
- package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
- package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
- package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
- package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
- package/dist/commands/sources/sources-cli.js +305 -0
- package/dist/commands/sources/stash-cli.js +219 -0
- package/dist/commands/sources/stash-skeleton.js +79 -0
- package/dist/commands/tasks/default-tasks.js +173 -0
- package/dist/commands/tasks/tasks-cli.js +210 -0
- package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
- package/dist/commands/wiki-cli.js +307 -0
- package/dist/commands/workflow-cli.js +329 -0
- package/dist/core/action-contributors.js +1 -1
- package/dist/core/assert.js +40 -0
- package/dist/core/asset/asset-create.js +54 -0
- package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
- package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
- package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
- package/dist/core/{markdown.js → asset/markdown.js} +1 -1
- package/dist/core/asset/stash-meta.js +110 -0
- package/dist/core/best-effort.js +64 -0
- package/dist/core/common.js +32 -18
- package/dist/core/{config-io.js → config/config-io.js} +29 -19
- package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
- package/dist/core/{config-schema.js → config/config-schema.js} +45 -1
- package/dist/core/config/config-types.js +16 -0
- package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
- package/dist/core/{config.js → config/config.js} +10 -8
- package/dist/core/env-secret-ref.js +90 -0
- package/dist/core/errors.js +13 -3
- package/dist/core/events.js +27 -4
- package/dist/core/file-lock.js +1 -1
- package/dist/core/improve-types.js +48 -0
- package/dist/core/lesson-lint.js +2 -2
- package/dist/core/paths.js +2 -2
- package/dist/{setup/ripgrep-install.js → core/ripgrep/install.js} +2 -2
- package/dist/{setup/ripgrep-resolve.js → core/ripgrep/resolve.js} +2 -2
- package/dist/core/state-db.js +88 -46
- package/dist/core/text-truncation.js +148 -0
- package/dist/core/time.js +1 -1
- package/dist/core/write-source.js +98 -85
- package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
- package/dist/indexer/{db.js → db/db.js} +126 -116
- package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
- package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
- package/dist/indexer/ensure-index.js +4 -4
- package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
- package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
- package/dist/indexer/indexer.js +37 -30
- package/dist/indexer/init.js +54 -0
- package/dist/indexer/manifest.js +10 -10
- package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +92 -23
- package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
- package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
- package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
- package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
- package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
- package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
- package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
- package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
- package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
- package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
- package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
- package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
- package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
- package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
- package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
- package/dist/indexer/{walker.js → walk/walker.js} +4 -3
- package/dist/integrations/agent/builder-shared.js +39 -0
- package/dist/integrations/agent/builders.js +14 -81
- package/dist/integrations/agent/config.js +6 -4
- package/dist/integrations/agent/detect.js +1 -1
- package/dist/integrations/agent/index.js +23 -8
- package/dist/integrations/agent/prompts.js +2 -3
- package/dist/integrations/agent/runner.js +22 -3
- package/dist/integrations/agent/spawn.js +9 -10
- package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
- package/dist/integrations/harnesses/claude/config-import.js +70 -0
- package/dist/integrations/harnesses/claude/index.js +64 -0
- package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +16 -1
- package/dist/integrations/harnesses/index.js +144 -0
- package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
- package/dist/integrations/harnesses/opencode/config-import.js +82 -0
- package/dist/integrations/harnesses/opencode/index.js +59 -0
- package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
- package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
- package/dist/integrations/harnesses/types.js +43 -0
- package/dist/integrations/lockfile.js +7 -16
- package/dist/integrations/session-logs/index.js +82 -9
- package/dist/llm/call-ai.js +4 -4
- package/dist/llm/client.js +131 -6
- package/dist/llm/embedder.js +6 -6
- package/dist/llm/embedders/local.js +9 -22
- package/dist/llm/embedders/remote.js +2 -2
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +31 -12
- package/dist/llm/index-passes.js +1 -1
- package/dist/llm/memory-infer.js +12 -5
- package/dist/llm/metadata-enhance.js +2 -2
- package/dist/output/context.js +6 -44
- package/dist/output/renderers.js +88 -58
- package/dist/output/shapes/curate.js +7 -3
- package/dist/output/shapes/distill.js +7 -3
- package/dist/output/shapes/env-list.js +18 -16
- package/dist/output/shapes/events.js +5 -4
- package/dist/output/shapes/helpers.js +2 -4
- package/dist/output/shapes/history.js +7 -3
- package/dist/output/shapes/passthrough.js +8 -11
- package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
- package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
- package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
- package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
- package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
- package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
- package/dist/output/shapes/registry-search.js +7 -3
- package/dist/output/shapes/registry.js +12 -0
- package/dist/output/shapes/search.js +7 -3
- package/dist/output/shapes/secret-list.js +18 -16
- package/dist/output/shapes/show.js +7 -3
- package/dist/output/shapes.js +55 -30
- package/dist/output/text/add.js +2 -3
- package/dist/output/text/clone.js +2 -3
- package/dist/output/text/config.js +2 -3
- package/dist/output/text/curate.js +4 -3
- package/dist/output/text/distill.js +2 -3
- package/dist/output/text/enable-disable.js +5 -4
- package/dist/output/text/env.js +13 -0
- package/dist/output/text/events.js +5 -4
- package/dist/output/text/feedback.js +4 -3
- package/dist/output/text/helpers.js +54 -39
- package/dist/output/text/history.js +2 -3
- package/dist/output/text/import.js +2 -3
- package/dist/output/text/index.js +2 -3
- package/dist/output/text/info.js +2 -3
- package/dist/output/text/init.js +2 -3
- package/dist/output/text/list.js +2 -3
- package/dist/output/text/proposal/producer.js +9 -0
- package/dist/output/text/proposal/proposal.js +13 -0
- package/dist/output/text/registry-commands.js +8 -7
- package/dist/output/text/registry.js +12 -0
- package/dist/output/text/remember.js +4 -3
- package/dist/output/text/remove.js +2 -3
- package/dist/output/text/save.js +2 -3
- package/dist/output/text/search.js +4 -3
- package/dist/output/text/show.js +4 -3
- package/dist/output/text/update.js +2 -3
- package/dist/output/text/upgrade.js +2 -3
- package/dist/output/text/wiki.js +12 -11
- package/dist/output/text/workflow.js +12 -10
- package/dist/output/text.js +66 -32
- package/dist/registry/build-index.js +11 -10
- package/dist/registry/factory.js +1 -1
- package/dist/registry/origin-resolve.js +1 -1
- package/dist/registry/providers/index.js +2 -2
- package/dist/registry/providers/skills-sh.js +91 -72
- package/dist/registry/providers/static-index.js +75 -52
- package/dist/registry/resolve.js +3 -3
- package/dist/runtime.js +242 -0
- package/dist/scripts/migrate-storage.js +1594 -673
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +240 -166
- package/dist/setup/detect.js +338 -9
- package/dist/setup/harness-config-import.js +56 -0
- package/dist/setup/registry-stash-loader.js +99 -0
- package/dist/setup/setup.js +664 -96
- package/dist/sources/include.js +1 -1
- package/dist/sources/provider-factory.js +2 -2
- package/dist/sources/providers/filesystem.js +3 -3
- package/dist/sources/providers/git.js +9 -9
- package/dist/sources/providers/index.js +4 -4
- package/dist/sources/providers/npm.js +6 -6
- package/dist/sources/providers/provider-utils.js +13 -20
- package/dist/sources/providers/sync-from-ref.js +5 -5
- package/dist/sources/providers/tar-utils.js +2 -2
- package/dist/sources/providers/website.js +2 -2
- package/dist/sources/resolve.js +5 -5
- package/dist/sources/website-ingest.js +5 -5
- package/dist/storage/database.js +102 -0
- package/dist/storage/engines/sqlite-migrations.js +42 -0
- package/dist/storage/locations.js +25 -0
- package/dist/storage/repositories/index-db.js +43 -0
- package/dist/storage/repositories/workflow-runs-repository.js +141 -0
- package/dist/tasks/backends/cron.js +4 -4
- package/dist/tasks/backends/exec-utils.js +32 -0
- package/dist/tasks/backends/index.js +3 -3
- package/dist/tasks/backends/launchd.js +7 -14
- package/dist/tasks/backends/schtasks.js +7 -16
- package/dist/tasks/embedded.js +71 -0
- package/dist/tasks/parser.js +2 -2
- package/dist/tasks/resolveAkmBin.js +1 -1
- package/dist/tasks/runner.js +28 -15
- package/dist/tasks/schedule.js +1 -1
- package/dist/tasks/validator.js +7 -7
- package/dist/text-import-hook.mjs +51 -0
- package/dist/version.js +2 -1
- package/dist/wiki/wiki.js +7 -7
- package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
- package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
- package/dist/workflows/cli.js +1 -1
- package/dist/workflows/db.js +50 -32
- package/dist/workflows/parser.js +4 -4
- package/dist/workflows/renderer.js +5 -5
- package/dist/workflows/runtime/agent-identity.js +56 -0
- package/dist/workflows/runtime/checkin.js +57 -0
- package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
- package/dist/workflows/validate-summary.js +82 -0
- package/docs/README.md +1 -1
- package/docs/data-and-telemetry.md +6 -6
- package/package.json +16 -8
- package/dist/commands/add-cli.js +0 -279
- package/dist/commands/env.js +0 -213
- package/dist/integrations/agent/sdk-runner.js +0 -126
- package/dist/output/shapes/vault-list.js +0 -19
- package/dist/output/text/proposal-producer.js +0 -8
- package/dist/output/text/proposal.js +0 -12
- package/dist/output/text/vault.js +0 -16
- /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
- /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
- /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
- /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
- /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
- /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
- /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
- /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
package/dist/setup/detect.js
CHANGED
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
* a result object describing what was found.
|
|
9
9
|
*/
|
|
10
10
|
import fs from "node:fs";
|
|
11
|
+
import os from "node:os";
|
|
11
12
|
import path from "node:path";
|
|
13
|
+
import { defaultWhich } from "../integrations/agent/detect.js";
|
|
14
|
+
import { SESSION_LOG_HARNESSES } from "../integrations/harnesses/index.js";
|
|
15
|
+
import { spawn } from "../runtime.js";
|
|
16
|
+
import { detectHarnessConfigs } from "./harness-config-import.js";
|
|
12
17
|
// ── Ollama Detection ────────────────────────────────────────────────────────
|
|
13
18
|
const OLLAMA_BASE = "http://localhost:11434";
|
|
14
19
|
/**
|
|
@@ -41,7 +46,7 @@ export async function detectOllama() {
|
|
|
41
46
|
}
|
|
42
47
|
// CLI fallback
|
|
43
48
|
try {
|
|
44
|
-
const proc =
|
|
49
|
+
const proc = spawn(["ollama", "list"], {
|
|
45
50
|
stdout: "pipe",
|
|
46
51
|
stderr: "pipe",
|
|
47
52
|
});
|
|
@@ -64,15 +69,52 @@ export async function detectOllama() {
|
|
|
64
69
|
}
|
|
65
70
|
return result;
|
|
66
71
|
}
|
|
72
|
+
const LMSTUDIO_BASE = "http://localhost:1234";
|
|
73
|
+
/**
|
|
74
|
+
* Detect if LM Studio is running and list available models.
|
|
75
|
+
* Probes the OpenAI-compatible /v1/models endpoint.
|
|
76
|
+
*/
|
|
77
|
+
export async function detectLMStudio() {
|
|
78
|
+
const result = { available: false, models: [], endpoint: LMSTUDIO_BASE };
|
|
79
|
+
try {
|
|
80
|
+
const response = await fetch(`${LMSTUDIO_BASE}/v1/models`, {
|
|
81
|
+
signal: AbortSignal.timeout(2000),
|
|
82
|
+
});
|
|
83
|
+
if (response.ok) {
|
|
84
|
+
const data = (await response.json());
|
|
85
|
+
if (Array.isArray(data.data)) {
|
|
86
|
+
result.models = data.data
|
|
87
|
+
.map((m) => (typeof m.id === "string" ? m.id : ""))
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.sort();
|
|
90
|
+
result.available = true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// LM Studio not running or not accessible
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
67
99
|
// ── Agent Platform Detection ────────────────────────────────────────────────
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Setup stash-source candidates, derived from the unified harness registry
|
|
102
|
+
* (#567).
|
|
103
|
+
*
|
|
104
|
+
* BEHAVIOUR FIX: the old hardcoded list named 6 harnesses (Claude Code,
|
|
105
|
+
* OpenCode, Continue, Codeium/Windsurf, Cursor, Codex CLI) but only the first
|
|
106
|
+
* two have a session-log provider. Selecting any of the other four added a
|
|
107
|
+
* filesystem stash source that was never indexed/extracted — a silent no-op
|
|
108
|
+
* "detection trap" (sessions never reached the improve pipeline).
|
|
109
|
+
*
|
|
110
|
+
* The registry is now the single source of which harnesses are real stash
|
|
111
|
+
* sources: a candidate must (a) have `capabilities.sessionLogs === true` AND
|
|
112
|
+
* (b) declare a `setupDetectionDir`. `SESSION_LOG_HARNESSES` already filters by
|
|
113
|
+
* (a), so dropping the four dead options is automatic — they aren't even in the
|
|
114
|
+
* registry. Adding a new session-log harness with a detection dir makes it
|
|
115
|
+
* appear here with no edit to this file.
|
|
116
|
+
*/
|
|
117
|
+
const AGENT_PLATFORMS = SESSION_LOG_HARNESSES.filter((h) => h.setupDetectionDir).map((h) => ({ name: h.displayName, relPath: h.setupDetectionDir }));
|
|
76
118
|
/**
|
|
77
119
|
* Scan the user's home directory for known agent platform config directories.
|
|
78
120
|
* Supports both HOME (Unix) and USERPROFILE (Windows).
|
|
@@ -94,3 +136,290 @@ export function detectAgentPlatforms() {
|
|
|
94
136
|
path: path.join(home, p.relPath),
|
|
95
137
|
}));
|
|
96
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Map of env var NAME → { provider, kind }. The key NAMES are the only thing
|
|
141
|
+
* this module ever inspects from `process.env`; values are never touched.
|
|
142
|
+
*/
|
|
143
|
+
const PROVIDER_ENV_VARS = [
|
|
144
|
+
{ name: "ANTHROPIC_API_KEY", provider: "anthropic", kind: "apiKey" },
|
|
145
|
+
{ name: "OPENAI_API_KEY", provider: "openai", kind: "apiKey" },
|
|
146
|
+
{ name: "GEMINI_API_KEY", provider: "gemini", kind: "apiKey" },
|
|
147
|
+
{ name: "GOOGLE_API_KEY", provider: "gemini", kind: "apiKey" },
|
|
148
|
+
{ name: "GROQ_API_KEY", provider: "groq", kind: "apiKey" },
|
|
149
|
+
{ name: "OLLAMA_HOST", provider: "ollama", kind: "endpoint" },
|
|
150
|
+
{ name: "OLLAMA_BASE_URL", provider: "ollama", kind: "endpoint" },
|
|
151
|
+
{ name: "LM_STUDIO_BASE_URL", provider: "lmstudio", kind: "endpoint" },
|
|
152
|
+
{ name: "LM_STUDIO_API_BASE", provider: "lmstudio", kind: "endpoint" },
|
|
153
|
+
{ name: "LMSTUDIO_BASE_URL", provider: "lmstudio", kind: "endpoint" },
|
|
154
|
+
{ name: "LMSTUDIO_API_BASE", provider: "lmstudio", kind: "endpoint" },
|
|
155
|
+
{ name: "AKM_LLM_API_KEY", provider: "akm-llm", kind: "apiKey" },
|
|
156
|
+
{ name: "AKM_LLM_ENDPOINT", provider: "akm-llm", kind: "endpoint" },
|
|
157
|
+
{ name: "AKM_LLM_BASE_URL", provider: "akm-llm", kind: "endpoint" },
|
|
158
|
+
];
|
|
159
|
+
/**
|
|
160
|
+
* Scan `process.env` for the presence of known provider configuration env var
|
|
161
|
+
* NAMES and return inferred providers.
|
|
162
|
+
*
|
|
163
|
+
* Pure function — no network, no filesystem. It reads only whether each known
|
|
164
|
+
* key is *defined and non-empty*; it never reads, returns, or logs the value.
|
|
165
|
+
*
|
|
166
|
+
* @param envSource Env to inspect. Defaults to `process.env`. Tests inject a
|
|
167
|
+
* fake env so a real API key is never required.
|
|
168
|
+
* @returns Inferred providers, each carrying the env var NAME only.
|
|
169
|
+
*/
|
|
170
|
+
export function scanProviderEnvVars(envSource = process.env) {
|
|
171
|
+
const results = [];
|
|
172
|
+
for (const entry of PROVIDER_ENV_VARS) {
|
|
173
|
+
const value = envSource[entry.name];
|
|
174
|
+
const present = typeof value === "string" && value.trim().length > 0;
|
|
175
|
+
if (!present)
|
|
176
|
+
continue;
|
|
177
|
+
results.push({ provider: entry.provider, envVar: entry.name, kind: entry.kind });
|
|
178
|
+
}
|
|
179
|
+
return results;
|
|
180
|
+
}
|
|
181
|
+
/** Default endpoints probed in addition to any harness-config base URLs. */
|
|
182
|
+
const DEFAULT_LOCAL_ENDPOINTS = [
|
|
183
|
+
{ baseUrl: "http://localhost:11434", label: "Ollama" },
|
|
184
|
+
{ baseUrl: "http://localhost:1234", label: "LM Studio" },
|
|
185
|
+
{ baseUrl: "http://localhost:8080", label: "Local (8080)" },
|
|
186
|
+
];
|
|
187
|
+
/**
|
|
188
|
+
* Pick a sensible default model from a list via a name heuristic.
|
|
189
|
+
*
|
|
190
|
+
* Preference order: an explicit "instruct" variant, then the longest name
|
|
191
|
+
* (a rough proxy for the larger / more-capable variant), then the first.
|
|
192
|
+
* Returns `undefined` for an empty list.
|
|
193
|
+
*/
|
|
194
|
+
export function pickDefaultModel(models) {
|
|
195
|
+
const cleaned = models.filter((m) => typeof m === "string" && m.trim().length > 0);
|
|
196
|
+
if (cleaned.length === 0)
|
|
197
|
+
return undefined;
|
|
198
|
+
const instruct = cleaned.filter((m) => /instruct/i.test(m));
|
|
199
|
+
const pool = instruct.length > 0 ? instruct : cleaned;
|
|
200
|
+
// Prefer the longest name as a proxy for the larger/most-specific variant,
|
|
201
|
+
// breaking ties by sort order for determinism.
|
|
202
|
+
return [...pool].sort((a, b) => b.length - a.length || a.localeCompare(b))[0];
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Probe a single OpenAI-compatible `/v1/models` endpoint.
|
|
206
|
+
*
|
|
207
|
+
* Tolerant of failure: any network/timeout/parse error yields an
|
|
208
|
+
* `available: false` result rather than throwing.
|
|
209
|
+
*/
|
|
210
|
+
export async function probeLocalEndpoint(baseUrl, label, timeoutMs = 2000) {
|
|
211
|
+
const result = { baseUrl, label, available: false, models: [] };
|
|
212
|
+
const url = `${baseUrl.replace(/\/$/, "")}/v1/models`;
|
|
213
|
+
try {
|
|
214
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
215
|
+
if (response.ok) {
|
|
216
|
+
const data = (await response.json());
|
|
217
|
+
if (Array.isArray(data.data)) {
|
|
218
|
+
result.models = data.data
|
|
219
|
+
.map((m) => (typeof m.id === "string" ? m.id : ""))
|
|
220
|
+
.filter(Boolean)
|
|
221
|
+
.sort();
|
|
222
|
+
result.available = true;
|
|
223
|
+
result.defaultModel = pickDefaultModel(result.models);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// Endpoint down/unreachable — leave available=false.
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Probe the default local endpoints (Ollama 11434, LM Studio 1234, generic
|
|
234
|
+
* 8080) plus any base URLs found in imported harness configs.
|
|
235
|
+
*
|
|
236
|
+
* Never throws: every endpoint being down yields a list of unavailable
|
|
237
|
+
* results, not an error.
|
|
238
|
+
*
|
|
239
|
+
* @param harnessBaseUrls Extra base URLs to probe (e.g. from harness configs).
|
|
240
|
+
*/
|
|
241
|
+
export async function detectLocalServers(harnessBaseUrls = []) {
|
|
242
|
+
const endpoints = [...DEFAULT_LOCAL_ENDPOINTS];
|
|
243
|
+
for (const raw of harnessBaseUrls) {
|
|
244
|
+
if (typeof raw !== "string" || raw.trim().length === 0)
|
|
245
|
+
continue;
|
|
246
|
+
// Strip a trailing /v1 (and trailing slash) so we probe consistently.
|
|
247
|
+
const baseUrl = raw.replace(/\/$/, "").replace(/\/v1$/, "");
|
|
248
|
+
if (!endpoints.some((e) => e.baseUrl === baseUrl)) {
|
|
249
|
+
endpoints.push({ baseUrl, label: `Harness (${baseUrl})` });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return Promise.all(endpoints.map((e) => probeLocalEndpoint(e.baseUrl, e.label)));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Suggest stash directories, ranked (lower rank = higher priority).
|
|
256
|
+
*
|
|
257
|
+
* Sources, in priority order:
|
|
258
|
+
* 1. An existing config `stashDir` (always rank 0 — no-op / keep current).
|
|
259
|
+
* 2. A `akm/` or `agent-stash/` directory in the CWD git repo.
|
|
260
|
+
* 3. `~/akm` then `~/.akm` when they already exist.
|
|
261
|
+
*
|
|
262
|
+
* Pure function — filesystem reads only, no network. Tests inject `cwd`/`home`.
|
|
263
|
+
*/
|
|
264
|
+
export function detectStashDir(opts) {
|
|
265
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
266
|
+
const home = opts?.home ?? os.homedir();
|
|
267
|
+
const suggestions = [];
|
|
268
|
+
const seen = new Set();
|
|
269
|
+
const push = (p, reason, rank) => {
|
|
270
|
+
const abs = path.resolve(p);
|
|
271
|
+
if (seen.has(abs))
|
|
272
|
+
return;
|
|
273
|
+
seen.add(abs);
|
|
274
|
+
suggestions.push({ path: abs, reason, rank });
|
|
275
|
+
};
|
|
276
|
+
if (opts?.existingStashDir?.trim()) {
|
|
277
|
+
push(opts.existingStashDir, "existing config stashDir", 0);
|
|
278
|
+
}
|
|
279
|
+
// CWD git repo containing akm/ or agent-stash/
|
|
280
|
+
const repoRoot = findGitRepoRoot(cwd);
|
|
281
|
+
if (repoRoot) {
|
|
282
|
+
for (const dirName of ["akm", "agent-stash"]) {
|
|
283
|
+
const candidate = path.join(repoRoot, dirName);
|
|
284
|
+
try {
|
|
285
|
+
if (fs.statSync(candidate).isDirectory()) {
|
|
286
|
+
push(candidate, `git repo contains ${dirName}/`, 1);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// not present
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// ~/akm then ~/.akm when they exist
|
|
295
|
+
if (home) {
|
|
296
|
+
for (const dirName of ["akm", ".akm"]) {
|
|
297
|
+
const candidate = path.join(home, dirName);
|
|
298
|
+
try {
|
|
299
|
+
if (fs.statSync(candidate).isDirectory()) {
|
|
300
|
+
push(candidate, `${dirName} exists in home`, 2);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// not present
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return suggestions.sort((a, b) => a.rank - b.rank);
|
|
309
|
+
}
|
|
310
|
+
/** Walk up from `start` looking for a directory containing `.git`. */
|
|
311
|
+
function findGitRepoRoot(start) {
|
|
312
|
+
let dir = path.resolve(start);
|
|
313
|
+
for (;;) {
|
|
314
|
+
try {
|
|
315
|
+
if (fs.existsSync(path.join(dir, ".git")))
|
|
316
|
+
return dir;
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
// ignore
|
|
320
|
+
}
|
|
321
|
+
const parent = path.dirname(dir);
|
|
322
|
+
if (parent === dir)
|
|
323
|
+
return undefined;
|
|
324
|
+
dir = parent;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Detect the best agent harness in priority order:
|
|
329
|
+
* 1. OpenCode SDK resolvable via `import('@opencode-ai/sdk')`.
|
|
330
|
+
* 2. `opencode` binary on PATH.
|
|
331
|
+
* 3. `claude` binary on PATH.
|
|
332
|
+
* 4. none.
|
|
333
|
+
*
|
|
334
|
+
* Pure aside from the dynamic import resolution (which performs no network).
|
|
335
|
+
*/
|
|
336
|
+
export async function detectHarness(whichFn = defaultWhich) {
|
|
337
|
+
try {
|
|
338
|
+
await import("@opencode-ai/sdk");
|
|
339
|
+
return "opencode-sdk";
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// SDK not installed — fall through to bin probes.
|
|
343
|
+
}
|
|
344
|
+
if (whichFn("opencode"))
|
|
345
|
+
return "opencode";
|
|
346
|
+
if (whichFn("claude"))
|
|
347
|
+
return "claude";
|
|
348
|
+
return "none";
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Run the full environment-detection pipeline once and return a single typed
|
|
352
|
+
* result. Orchestrates env-var scan, harness config import, harness selection,
|
|
353
|
+
* local-server probes, and stash-dir suggestions.
|
|
354
|
+
*
|
|
355
|
+
* SAFETY: No API key VALUE is ever read, stored, logged, or returned — only
|
|
356
|
+
* env var NAMES. Tolerant of every detector failing.
|
|
357
|
+
*
|
|
358
|
+
* @param opts.existingStashDir Current config stashDir (no-op suggestion).
|
|
359
|
+
* @param opts.envSource Env to scan. Defaults to `process.env`.
|
|
360
|
+
* @param opts.whichFn Binary lookup. Tests inject a stub.
|
|
361
|
+
*/
|
|
362
|
+
export async function detectEnvironment(opts) {
|
|
363
|
+
const envSource = opts?.envSource ?? process.env;
|
|
364
|
+
const whichFn = opts?.whichFn ?? defaultWhich;
|
|
365
|
+
let harnessConfigs = [];
|
|
366
|
+
try {
|
|
367
|
+
harnessConfigs = detectHarnessConfigs();
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
harnessConfigs = [];
|
|
371
|
+
}
|
|
372
|
+
const harnessBaseUrls = harnessConfigs.map((c) => c.baseUrl).filter((u) => typeof u === "string");
|
|
373
|
+
const [harness, localServers] = await Promise.all([
|
|
374
|
+
detectHarness(whichFn),
|
|
375
|
+
detectLocalServers(harnessBaseUrls).catch(() => []),
|
|
376
|
+
]);
|
|
377
|
+
let agentPlatforms = [];
|
|
378
|
+
try {
|
|
379
|
+
agentPlatforms = detectAgentPlatforms();
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
agentPlatforms = [];
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
harness,
|
|
386
|
+
providers: scanProviderEnvVars(envSource),
|
|
387
|
+
harnessConfigs,
|
|
388
|
+
localServers,
|
|
389
|
+
stashSuggestions: detectStashDir({
|
|
390
|
+
existingStashDir: opts?.existingStashDir,
|
|
391
|
+
cwd: opts?.cwd,
|
|
392
|
+
home: opts?.home,
|
|
393
|
+
}),
|
|
394
|
+
agentPlatforms,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Render a compact, human-readable "Detected environment" summary block.
|
|
399
|
+
* Contains env var NAMES only — never any value.
|
|
400
|
+
*/
|
|
401
|
+
export function renderDetectionSummary(env) {
|
|
402
|
+
const lines = ["Detected environment:"];
|
|
403
|
+
lines.push(` Harness: ${env.harness}`);
|
|
404
|
+
const liveServers = env.localServers.filter((s) => s.available);
|
|
405
|
+
if (liveServers.length > 0) {
|
|
406
|
+
lines.push(` Local servers: ${liveServers
|
|
407
|
+
.map((s) => `${s.label}${s.defaultModel ? ` (${s.defaultModel})` : ""}`)
|
|
408
|
+
.join(", ")}`);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
lines.push(" Local servers: none reachable");
|
|
412
|
+
}
|
|
413
|
+
if (env.providers.length > 0) {
|
|
414
|
+
// NAMES only — never values.
|
|
415
|
+
lines.push(` Provider keys: ${env.providers.map((p) => `${p.provider}:${p.envVar}`).join(", ")}`);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
lines.push(" Provider keys: none in environment");
|
|
419
|
+
}
|
|
420
|
+
const topStash = env.stashSuggestions[0];
|
|
421
|
+
if (topStash) {
|
|
422
|
+
lines.push(` Stash suggest: ${topStash.path} (${topStash.reason})`);
|
|
423
|
+
}
|
|
424
|
+
return lines.join("\n");
|
|
425
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Pluggable registry of LLM config importers for supported agent harnesses.
|
|
6
|
+
*
|
|
7
|
+
* Each importer detects whether a harness is installed (filesystem only,
|
|
8
|
+
* no network) and, if so, reads its config to extract LLM connection details.
|
|
9
|
+
* API key VALUES are never stored — only the env var names that hold them.
|
|
10
|
+
*
|
|
11
|
+
* To add a new harness: implement {@link HarnessConfigImporter} and append it
|
|
12
|
+
* to {@link HARNESS_CONFIG_IMPORTERS}.
|
|
13
|
+
*
|
|
14
|
+
* NOTE: The `detect()` method in each importer overlaps intentionally with
|
|
15
|
+
* `detectAgentPlatforms()` in `detect.ts`. That function scans for harness
|
|
16
|
+
* presence to display installed platforms to the user; these importers go
|
|
17
|
+
* further by reading and parsing the harness config. They serve different
|
|
18
|
+
* purposes and should not be deduplicated.
|
|
19
|
+
*/
|
|
20
|
+
import { claudeCodeImporter } from "../integrations/harnesses/claude/config-import.js";
|
|
21
|
+
import { openCodeImporter } from "../integrations/harnesses/opencode/config-import.js";
|
|
22
|
+
// The Claude Code importer was migrated to its harness directory in #563
|
|
23
|
+
// (`harnesses/claude/config-import.ts`) and the OpenCode importer in #564
|
|
24
|
+
// (`harnesses/opencode/config-import.ts`). Both are imported back into
|
|
25
|
+
// HARNESS_CONFIG_IMPORTERS below so detection order is unchanged.
|
|
26
|
+
// ── Registry ─────────────────────────────────────────────────────────────────
|
|
27
|
+
/**
|
|
28
|
+
* Registry of all supported harness config importers.
|
|
29
|
+
* To add a new harness: implement {@link HarnessConfigImporter} and append here.
|
|
30
|
+
*/
|
|
31
|
+
export const HARNESS_CONFIG_IMPORTERS = [claudeCodeImporter, openCodeImporter];
|
|
32
|
+
/**
|
|
33
|
+
* Run all importers whose `detect()` returns `true` and collect their configs.
|
|
34
|
+
*
|
|
35
|
+
* Pure function — filesystem reads only, no network, no side effects.
|
|
36
|
+
* Individual importer failures are swallowed so one broken harness never
|
|
37
|
+
* blocks the setup wizard.
|
|
38
|
+
*
|
|
39
|
+
* @returns List of detected harness configs (may be empty).
|
|
40
|
+
*/
|
|
41
|
+
export function detectHarnessConfigs() {
|
|
42
|
+
const results = [];
|
|
43
|
+
for (const importer of HARNESS_CONFIG_IMPORTERS) {
|
|
44
|
+
try {
|
|
45
|
+
if (!importer.detect())
|
|
46
|
+
continue;
|
|
47
|
+
const config = importer.importConfig();
|
|
48
|
+
if (config)
|
|
49
|
+
results.push(config);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Never let one importer crash the whole detection
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Registry-driven stash discovery for the setup wizard.
|
|
6
|
+
*
|
|
7
|
+
* Fetches the list of available stashes from the official AKM registry,
|
|
8
|
+
* using a cached result when available. Falls back to FALLBACK_STASHES
|
|
9
|
+
* when the registry is unreachable or returns no results.
|
|
10
|
+
*
|
|
11
|
+
* Adding a new default-selected stash: append its registry ID to
|
|
12
|
+
* DEFAULT_SELECTED_STASH_IDS below. No other change required.
|
|
13
|
+
*/
|
|
14
|
+
// ── Default selections ──────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Registry stash IDs that are pre-selected by default during setup.
|
|
17
|
+
* To add a new default stash: append its registry ID here.
|
|
18
|
+
* IDs must match the `id` field in the official registry index.
|
|
19
|
+
*
|
|
20
|
+
* This is the single source of truth for which stashes are pre-checked
|
|
21
|
+
* in the setup wizard. No other change is required to adjust defaults.
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_SELECTED_STASH_IDS = ["itlackey/akm-stash"];
|
|
24
|
+
// ── Fallback list ───────────────────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* Hardcoded stash list used when the registry is unreachable.
|
|
27
|
+
* Mirrors the previous RECOMMENDED_GITHUB_REPOS constant.
|
|
28
|
+
*/
|
|
29
|
+
const FALLBACK_STASHES = [
|
|
30
|
+
{
|
|
31
|
+
id: "itlackey/akm-stash",
|
|
32
|
+
name: "itlackey/akm-stash",
|
|
33
|
+
description: "Official AKM onboarding stash",
|
|
34
|
+
url: "https://github.com/itlackey/akm-stash",
|
|
35
|
+
source: "fallback",
|
|
36
|
+
defaultSelected: true,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "andrewyng/context-hub",
|
|
40
|
+
name: "andrewyng/context-hub",
|
|
41
|
+
description: "Optional community prompt and context stash",
|
|
42
|
+
url: "https://github.com/andrewyng/context-hub",
|
|
43
|
+
source: "fallback",
|
|
44
|
+
defaultSelected: false,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
// ── Loader ──────────────────────────────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Fetch available stashes from the registry and map to SetupStashEntry[].
|
|
50
|
+
*
|
|
51
|
+
* Falls back to FALLBACK_STASHES on network failure, parse error, or
|
|
52
|
+
* empty response — setup never crashes due to a registry outage.
|
|
53
|
+
*
|
|
54
|
+
* @param registryUrl URL of the registry index JSON.
|
|
55
|
+
* @param timeoutMs Fetch timeout in ms (default: 4000).
|
|
56
|
+
*/
|
|
57
|
+
export async function loadSetupStashes(registryUrl, timeoutMs = 4000) {
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetch(registryUrl, {
|
|
60
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
61
|
+
headers: { Accept: "application/json" },
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok)
|
|
64
|
+
return FALLBACK_STASHES;
|
|
65
|
+
const raw = (await response.json());
|
|
66
|
+
if (!Array.isArray(raw.stashes) || raw.stashes.length === 0)
|
|
67
|
+
return FALLBACK_STASHES;
|
|
68
|
+
const entries = raw.stashes.flatMap((item) => {
|
|
69
|
+
if (!item || typeof item !== "object")
|
|
70
|
+
return [];
|
|
71
|
+
const s = item;
|
|
72
|
+
const id = typeof s.id === "string" ? s.id : "";
|
|
73
|
+
const name = typeof s.name === "string" ? s.name : id;
|
|
74
|
+
const description = typeof s.description === "string" ? s.description : "";
|
|
75
|
+
// Prefer github/git source URL built from the ref; fall back to homepage
|
|
76
|
+
const url = (s.source === "github" || s.source === "git") && typeof s.ref === "string"
|
|
77
|
+
? `https://github.com/${s.ref.replace(/^github:/, "")}`
|
|
78
|
+
: typeof s.homepage === "string"
|
|
79
|
+
? s.homepage
|
|
80
|
+
: "";
|
|
81
|
+
if (!id || !url)
|
|
82
|
+
return [];
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
id,
|
|
86
|
+
name,
|
|
87
|
+
description,
|
|
88
|
+
url,
|
|
89
|
+
source: "registry",
|
|
90
|
+
defaultSelected: DEFAULT_SELECTED_STASH_IDS.includes(id),
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
});
|
|
94
|
+
return entries.length > 0 ? entries : FALLBACK_STASHES;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return FALLBACK_STASHES;
|
|
98
|
+
}
|
|
99
|
+
}
|