@open-mercato/cezar 0.1.5-pr506.357
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 +573 -0
- package/dist/config.d.ts +144 -0
- package/dist/config.js +106 -0
- package/dist/config.js.map +1 -0
- package/dist/core/agent-runner.d.ts +169 -0
- package/dist/core/agent-runner.js +23 -0
- package/dist/core/agent-runner.js.map +1 -0
- package/dist/core/backend-detect.d.ts +16 -0
- package/dist/core/backend-detect.js +118 -0
- package/dist/core/backend-detect.js.map +1 -0
- package/dist/core/claude-cli-runner.d.ts +56 -0
- package/dist/core/claude-cli-runner.js +416 -0
- package/dist/core/claude-cli-runner.js.map +1 -0
- package/dist/core/claude-ui-mapper.d.ts +71 -0
- package/dist/core/claude-ui-mapper.js +567 -0
- package/dist/core/claude-ui-mapper.js.map +1 -0
- package/dist/core/codex-app-server-runner.d.ts +29 -0
- package/dist/core/codex-app-server-runner.js +489 -0
- package/dist/core/codex-app-server-runner.js.map +1 -0
- package/dist/core/codex-ui-mapper.d.ts +63 -0
- package/dist/core/codex-ui-mapper.js +491 -0
- package/dist/core/codex-ui-mapper.js.map +1 -0
- package/dist/core/ndjson.d.ts +6 -0
- package/dist/core/ndjson.js +23 -0
- package/dist/core/ndjson.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +30 -0
- package/dist/core/opencode-server-runner.js +488 -0
- package/dist/core/opencode-server-runner.js.map +1 -0
- package/dist/core/opencode-ui-mapper.d.ts +105 -0
- package/dist/core/opencode-ui-mapper.js +588 -0
- package/dist/core/opencode-ui-mapper.js.map +1 -0
- package/dist/core/process-usage.d.ts +63 -0
- package/dist/core/process-usage.js +181 -0
- package/dist/core/process-usage.js.map +1 -0
- package/dist/core/runner-factory.d.ts +8 -0
- package/dist/core/runner-factory.js +22 -0
- package/dist/core/runner-factory.js.map +1 -0
- package/dist/core/tool-display.d.ts +30 -0
- package/dist/core/tool-display.js +156 -0
- package/dist/core/tool-display.js.map +1 -0
- package/dist/core/ui-events.d.ts +308 -0
- package/dist/core/ui-events.js +25 -0
- package/dist/core/ui-events.js.map +1 -0
- package/dist/core/usage.d.ts +11 -0
- package/dist/core/usage.js +15 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/git-worktree.d.ts +88 -0
- package/dist/git-worktree.js +297 -0
- package/dist/git-worktree.js.map +1 -0
- package/dist/handoff.d.ts +62 -0
- package/dist/handoff.js +147 -0
- package/dist/handoff.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +466 -0
- package/dist/index.js.map +1 -0
- package/dist/install-as-command.d.ts +39 -0
- package/dist/install-as-command.js +51 -0
- package/dist/install-as-command.js.map +1 -0
- package/dist/pack-check.d.ts +17 -0
- package/dist/pack-check.js +27 -0
- package/dist/pack-check.js.map +1 -0
- package/dist/paths.d.ts +23 -0
- package/dist/paths.js +34 -0
- package/dist/paths.js.map +1 -0
- package/dist/planner.d.ts +17 -0
- package/dist/planner.js +248 -0
- package/dist/planner.js.map +1 -0
- package/dist/release/snapshot.d.ts +64 -0
- package/dist/release/snapshot.js +75 -0
- package/dist/release/snapshot.js.map +1 -0
- package/dist/release/stable.d.ts +46 -0
- package/dist/release/stable.js +57 -0
- package/dist/release/stable.js.map +1 -0
- package/dist/runs/auto-name.d.ts +75 -0
- package/dist/runs/auto-name.js +168 -0
- package/dist/runs/auto-name.js.map +1 -0
- package/dist/runs/retention.d.ts +60 -0
- package/dist/runs/retention.js +86 -0
- package/dist/runs/retention.js.map +1 -0
- package/dist/runs/review-gate.d.ts +18 -0
- package/dist/runs/review-gate.js +21 -0
- package/dist/runs/review-gate.js.map +1 -0
- package/dist/runs/store.d.ts +408 -0
- package/dist/runs/store.js +526 -0
- package/dist/runs/store.js.map +1 -0
- package/dist/runs/task-refs.d.ts +24 -0
- package/dist/runs/task-refs.js +74 -0
- package/dist/runs/task-refs.js.map +1 -0
- package/dist/runs/ui-event-sink.d.ts +81 -0
- package/dist/runs/ui-event-sink.js +210 -0
- package/dist/runs/ui-event-sink.js.map +1 -0
- package/dist/server/capabilities.d.ts +33 -0
- package/dist/server/capabilities.js +43 -0
- package/dist/server/capabilities.js.map +1 -0
- package/dist/server/forge/github.d.ts +70 -0
- package/dist/server/forge/github.js +375 -0
- package/dist/server/forge/github.js.map +1 -0
- package/dist/server/forge/index.d.ts +24 -0
- package/dist/server/forge/index.js +45 -0
- package/dist/server/forge/index.js.map +1 -0
- package/dist/server/forge/types.d.ts +80 -0
- package/dist/server/forge/types.js +2 -0
- package/dist/server/forge/types.js.map +1 -0
- package/dist/server/git-changes.d.ts +195 -0
- package/dist/server/git-changes.js +497 -0
- package/dist/server/git-changes.js.map +1 -0
- package/dist/server/git.d.ts +26 -0
- package/dist/server/git.js +103 -0
- package/dist/server/git.js.map +1 -0
- package/dist/server/github.d.ts +8 -0
- package/dist/server/github.js +8 -0
- package/dist/server/github.js.map +1 -0
- package/dist/server/launch-key.d.ts +7 -0
- package/dist/server/launch-key.js +33 -0
- package/dist/server/launch-key.js.map +1 -0
- package/dist/server/open-in-app.d.ts +51 -0
- package/dist/server/open-in-app.js +189 -0
- package/dist/server/open-in-app.js.map +1 -0
- package/dist/server/open-in-terminal.d.ts +25 -0
- package/dist/server/open-in-terminal.js +120 -0
- package/dist/server/open-in-terminal.js.map +1 -0
- package/dist/server/pr.d.ts +7 -0
- package/dist/server/pr.js +7 -0
- package/dist/server/pr.js.map +1 -0
- package/dist/server/server.d.ts +41 -0
- package/dist/server/server.js +1390 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/static-ui.d.ts +49 -0
- package/dist/server/static-ui.js +92 -0
- package/dist/server/static-ui.js.map +1 -0
- package/dist/server/wsl.d.ts +25 -0
- package/dist/server/wsl.js +92 -0
- package/dist/server/wsl.js.map +1 -0
- package/dist/server-install/engine.d.ts +33 -0
- package/dist/server-install/engine.js +314 -0
- package/dist/server-install/engine.js.map +1 -0
- package/dist/server-install/platforms/macosx-ngrok.d.ts +6 -0
- package/dist/server-install/platforms/macosx-ngrok.js +345 -0
- package/dist/server-install/platforms/macosx-ngrok.js.map +1 -0
- package/dist/server-install/platforms/ubuntu-vps.d.ts +39 -0
- package/dist/server-install/platforms/ubuntu-vps.js +746 -0
- package/dist/server-install/platforms/ubuntu-vps.js.map +1 -0
- package/dist/server-install/state.d.ts +26 -0
- package/dist/server-install/state.js +125 -0
- package/dist/server-install/state.js.map +1 -0
- package/dist/server-install/steps.d.ts +109 -0
- package/dist/server-install/steps.js +330 -0
- package/dist/server-install/steps.js.map +1 -0
- package/dist/server-install/strategies.d.ts +4 -0
- package/dist/server-install/strategies.js +20 -0
- package/dist/server-install/strategies.js.map +1 -0
- package/dist/server-install/types.d.ts +597 -0
- package/dist/server-install/types.js +98 -0
- package/dist/server-install/types.js.map +1 -0
- package/dist/server-install/ui.d.ts +42 -0
- package/dist/server-install/ui.js +130 -0
- package/dist/server-install/ui.js.map +1 -0
- package/dist/skills-banner.d.ts +21 -0
- package/dist/skills-banner.js +40 -0
- package/dist/skills-banner.js.map +1 -0
- package/dist/skills-remote.d.ts +35 -0
- package/dist/skills-remote.js +266 -0
- package/dist/skills-remote.js.map +1 -0
- package/dist/skills.d.ts +45 -0
- package/dist/skills.js +182 -0
- package/dist/skills.js.map +1 -0
- package/dist/todos.d.ts +63 -0
- package/dist/todos.js +168 -0
- package/dist/todos.js.map +1 -0
- package/dist/ui-state.d.ts +12 -0
- package/dist/ui-state.js +25 -0
- package/dist/ui-state.js.map +1 -0
- package/dist/update-check.d.ts +12 -0
- package/dist/update-check.js +41 -0
- package/dist/update-check.js.map +1 -0
- package/dist/workflows/load.d.ts +15 -0
- package/dist/workflows/load.js +56 -0
- package/dist/workflows/load.js.map +1 -0
- package/dist/workflows/run.d.ts +281 -0
- package/dist/workflows/run.js +1511 -0
- package/dist/workflows/run.js.map +1 -0
- package/dist/workflows/types.d.ts +307 -0
- package/dist/workflows/types.js +171 -0
- package/dist/workflows/types.js.map +1 -0
- package/package.json +90 -0
- package/scripts/check-pack.mjs +46 -0
- package/scripts/dev.mjs +74 -0
- package/scripts/install-as-command.mjs +120 -0
- package/scripts/mock-claude.mjs +310 -0
- package/scripts/release-snapshot.mjs +130 -0
- package/scripts/release.mjs +119 -0
- package/scripts/test-process-usage.mjs +47 -0
- package/web/dist/assets/arrow-left-BE957rJs.js +1 -0
- package/web/dist/assets/bundle-mjs-DLPpRFyl.js +1 -0
- package/web/dist/assets/centered-state-BddhEEtO.js +43 -0
- package/web/dist/assets/chunk-BO2N2NFS-f5igTtpB.js +131 -0
- package/web/dist/assets/command-CjhFbABo.js +1 -0
- package/web/dist/assets/compare-variants-Cs52IAEG.js +1 -0
- package/web/dist/assets/core-BuMqAF_1.js +12 -0
- package/web/dist/assets/css-BsVw1vtW.js +1 -0
- package/web/dist/assets/dialog-DFvXTkFv.js +1 -0
- package/web/dist/assets/diff-view-C9b8AITq.js +4 -0
- package/web/dist/assets/diff-woXpYk--.js +1 -0
- package/web/dist/assets/dist-FBs4wmIY.js +1 -0
- package/web/dist/assets/dist-o_kVMXjg.js +1 -0
- package/web/dist/assets/engine-javascript-FQvzRocB.js +141 -0
- package/web/dist/assets/file-BVuztAOd.js +1 -0
- package/web/dist/assets/folder-0pJ1NZ_S.js +1 -0
- package/web/dist/assets/git-pull-request-DJkAMT0f.js +1 -0
- package/web/dist/assets/git-toolbar-CwhQdx3J.js +1 -0
- package/web/dist/assets/github-D0rAX4-r.js +1 -0
- package/web/dist/assets/go-rLFTqkRN.js +1 -0
- package/web/dist/assets/highlighted-body-OFNGDK62-DGHHie_q.js +1 -0
- package/web/dist/assets/highlighter-BKVEsACy.js +3 -0
- package/web/dist/assets/html-BY76lXLH.js +1 -0
- package/web/dist/assets/index-CEkI7QxN.css +2 -0
- package/web/dist/assets/index-CO2lH2et.js +11 -0
- package/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/web/dist/assets/javascript-BgS3c2Ky.js +1 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/web/dist/assets/json-qhed-kSA.js +1 -0
- package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
- package/web/dist/assets/jsx-Bz0zcwM4.js +1 -0
- package/web/dist/assets/lib-CNGa3Dig.js +1 -0
- package/web/dist/assets/loader-circle-BV3DO9mE.js +1 -0
- package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
- package/web/dist/assets/markdown-D4-ADgH9.js +1 -0
- package/web/dist/assets/mermaid-GHXKKRXX-DM2VsGBG.js +1 -0
- package/web/dist/assets/open-mercato-toBr6SOa.svg +11 -0
- package/web/dist/assets/prompt-templates-C7ptKvDd.js +15 -0
- package/web/dist/assets/python-gzcpVVnB.js +1 -0
- package/web/dist/assets/react-dom-Ddtik4qM.js +1 -0
- package/web/dist/assets/refresh-cw-DCa-Rlwz.js +1 -0
- package/web/dist/assets/repo-git-DIhryGDx.js +1 -0
- package/web/dist/assets/run-diff-Dyow8B1q.js +3 -0
- package/web/dist/assets/run-header-jx9-18b8.js +1 -0
- package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
- package/web/dist/assets/search-x-CoCyl7zT.js +1 -0
- package/web/dist/assets/shellscript-CLZ0U2zV.js +1 -0
- package/web/dist/assets/skill-detail-DB7x_FQp.js +1 -0
- package/web/dist/assets/skills-98vCroU8.js +1 -0
- package/web/dist/assets/skills-BKIoqD6K.js +1 -0
- package/web/dist/assets/sql-BsFa4tDR.js +1 -0
- package/web/dist/assets/square-terminal-B6PZX4Qp.js +1 -0
- package/web/dist/assets/tab-link-C5--YMQd.js +1 -0
- package/web/dist/assets/task-changes-ejxPGVJw.js +1 -0
- package/web/dist/assets/task-commits-C6GO5qT-.js +1 -0
- package/web/dist/assets/task-files-CatpCBXa.js +2 -0
- package/web/dist/assets/task-thread-BMbXDM3d.js +5 -0
- package/web/dist/assets/tasks-table-dgezlCcg.js +1 -0
- package/web/dist/assets/trash-2-CDtxmmc8.js +1 -0
- package/web/dist/assets/triangle-alert-6su4Js5O.js +1 -0
- package/web/dist/assets/tsx-udAQXfEw.js +1 -0
- package/web/dist/assets/typescript-bsJCZSQ-.js +1 -0
- package/web/dist/assets/upload-Bp7hQxll.js +1 -0
- package/web/dist/assets/use-desktop-CvTwojOJ.js +3 -0
- package/web/dist/assets/utils-rQGbOrwc.js +1 -0
- package/web/dist/assets/workflows-Dy2c6790.js +11 -0
- package/web/dist/assets/x-Br_jIDBw.js +1 -0
- package/web/dist/assets/yaml-rwi0_p6S.js +1 -0
- package/web/dist/index.html +70 -0
- package/web/open-mercato.svg +11 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Contracts for the server installer (spec 2026-07-16-server-installer).
|
|
4
|
+
* The engine closes over `InstallStep` / `PlatformStrategy` and never learns
|
|
5
|
+
* what a step *does* — only `check` / `run` / `undo`. That seam is what makes
|
|
6
|
+
* "use ubuntu as a selector and run it that way" a registry lookup, and what
|
|
7
|
+
* makes the interactive helpers reusable across every future platform.
|
|
8
|
+
*/
|
|
9
|
+
/** The platforms the registry knows. Extend here + add a strategy file. */
|
|
10
|
+
export const PLATFORM_IDS = ['ubuntu-vps', 'macosx-ngrok'];
|
|
11
|
+
/** Per-step lifecycle. `failed` resumes identically to `pending`. */
|
|
12
|
+
export const STEP_STATUSES = ['pending', 'done', 'skipped', 'failed'];
|
|
13
|
+
/**
|
|
14
|
+
* One thing a step created, tagged by who owns its removal:
|
|
15
|
+
* - `owned` — cezar authored it and nothing else uses it → uninstall removes it.
|
|
16
|
+
* - `shared` — a system tool the operator may now depend on (gh, agent CLIs,
|
|
17
|
+
* certbot + its renewal timer, the cert itself) → uninstall lists it with a
|
|
18
|
+
* manual removal hint instead of yanking it.
|
|
19
|
+
*/
|
|
20
|
+
export const stepArtifactSchema = z.object({
|
|
21
|
+
kind: z.enum(['owned', 'shared']),
|
|
22
|
+
/** file | package | service | cert | htpasswd | config | note */
|
|
23
|
+
type: z.string().min(1),
|
|
24
|
+
/** Filesystem path for `owned` files (vhost, unit, htpasswd). */
|
|
25
|
+
path: z.string().optional(),
|
|
26
|
+
/** Human name: package id, unit name, cert domain, auth user. */
|
|
27
|
+
name: z.string().optional(),
|
|
28
|
+
/** `system` | `user` for services. */
|
|
29
|
+
scope: z.string().optional(),
|
|
30
|
+
/** For `shared` artifacts: the exact command the operator can run to remove it. */
|
|
31
|
+
removeHint: z.string().optional(),
|
|
32
|
+
});
|
|
33
|
+
/** What a step returns from `run()`; `undo()` receives it back verbatim. */
|
|
34
|
+
export const stepCreatedSchema = z
|
|
35
|
+
.object({ artifacts: z.array(stepArtifactSchema).default([]) })
|
|
36
|
+
.nullable();
|
|
37
|
+
/**
|
|
38
|
+
* Persisted per-step outcome — the `steps` map in `server.json`. A status this
|
|
39
|
+
* version doesn't know (written by a newer cezar) degrades to `failed`, which
|
|
40
|
+
* keeps the record AND keeps it on uninstall's undo path — never to a parse
|
|
41
|
+
* failure that would discard the whole ledger.
|
|
42
|
+
*/
|
|
43
|
+
export const stepOutcomeSchema = z.object({
|
|
44
|
+
status: z.enum(STEP_STATUSES).catch('failed'),
|
|
45
|
+
created: stepCreatedSchema.optional().catch(null),
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* `~/.cezar/server.json` — host-level, install-once. Additive-safe: every new
|
|
49
|
+
* field is optional / defaulted so an older cezar still parses a newer file
|
|
50
|
+
* (BACKWARD_COMPATIBILITY cross-version-state rule). No secrets live here.
|
|
51
|
+
*/
|
|
52
|
+
export const serverStateSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
schema: z.literal(1).catch(1),
|
|
55
|
+
/**
|
|
56
|
+
* Free string, not an enum: a `server.json` written by a newer cezar with a
|
|
57
|
+
* platform this version doesn't ship must still parse — the registry lookup
|
|
58
|
+
* is where "unknown platform" is decided, with the ledger intact.
|
|
59
|
+
*/
|
|
60
|
+
platform: z.string().min(1).optional().catch(undefined),
|
|
61
|
+
/** Flips true only when every required step is `done`. */
|
|
62
|
+
installed: z.boolean().default(false).catch(false),
|
|
63
|
+
/** True when this record was written by a CEZ_DRY_RUN preview — a real
|
|
64
|
+
* install/uninstall treats it as no record at all (self-healing). */
|
|
65
|
+
dryRun: z.boolean().optional().catch(undefined),
|
|
66
|
+
/** ISO stamp set by the caller (Date.now is unavailable in some contexts). */
|
|
67
|
+
createdAt: z.string().optional().catch(undefined),
|
|
68
|
+
updatedAt: z.string().optional().catch(undefined),
|
|
69
|
+
primaryPort: z.number().int().positive().default(4321).catch(4321),
|
|
70
|
+
/** Public URL / identity user surfaced at the end — display only. */
|
|
71
|
+
publicUrl: z.string().optional().catch(undefined),
|
|
72
|
+
/** macOS+ngrok free tier: the tunnel URL changes across restarts. */
|
|
73
|
+
ephemeral: z.boolean().optional().catch(undefined),
|
|
74
|
+
// A single malformed entry degrades to a `failed` record (undo still runs
|
|
75
|
+
// from constants), never to losing every other step's ledger.
|
|
76
|
+
steps: z
|
|
77
|
+
.record(z.string(), stepOutcomeSchema.catch({ status: 'failed', created: null }))
|
|
78
|
+
.default({})
|
|
79
|
+
.catch({}),
|
|
80
|
+
})
|
|
81
|
+
// Unknown top-level fields written by a newer cezar survive a load+save
|
|
82
|
+
// round-trip — the file's own additive-safe contract.
|
|
83
|
+
.passthrough();
|
|
84
|
+
/** Freshly-initialized state (no install yet). */
|
|
85
|
+
export function freshServerState() {
|
|
86
|
+
return serverStateSchema.parse({});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The interactive surface a step talks to. Implemented by `ui.ts` over
|
|
90
|
+
* `@clack/prompts`; declared here as a pure interface so `types.ts` (and the
|
|
91
|
+
* engine) never import the TUI library. Prompt methods resolve to the
|
|
92
|
+
* `CANCEL` sentinel instead of throwing when the user aborts.
|
|
93
|
+
*/
|
|
94
|
+
export const CANCEL = Symbol('cezar.ui.cancel');
|
|
95
|
+
/** Thrown by `preflight` to stop with a clean, user-facing reason (no stack). */
|
|
96
|
+
export class PreflightError extends Error {
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/server-install/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as clack from '@clack/prompts';
|
|
2
|
+
import { type Ui } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The interactive surface, implemented over `@clack/prompts` — the one place
|
|
5
|
+
* the TUI library is imported. `types.ts`, the engine and the steps talk to
|
|
6
|
+
* the `Ui` interface only, so `@clack/prompts` never enters the server/runtime
|
|
7
|
+
* import graph (AGENTS.md keeps that stack tiny).
|
|
8
|
+
*
|
|
9
|
+
* Every prompt maps clack's cancel symbol to the `CANCEL` sentinel instead of
|
|
10
|
+
* throwing, so a Ctrl-C mid-install is a value the engine can persist-and-exit
|
|
11
|
+
* on, not an exception.
|
|
12
|
+
*/
|
|
13
|
+
/** The subset of `@clack/prompts` the UI uses — injectable for unit tests. */
|
|
14
|
+
export interface PromptBackend {
|
|
15
|
+
intro: typeof clack.intro;
|
|
16
|
+
outro: typeof clack.outro;
|
|
17
|
+
note: typeof clack.note;
|
|
18
|
+
log: typeof clack.log;
|
|
19
|
+
select: typeof clack.select;
|
|
20
|
+
multiselect: typeof clack.multiselect;
|
|
21
|
+
confirm: typeof clack.confirm;
|
|
22
|
+
text: typeof clack.text;
|
|
23
|
+
password: typeof clack.password;
|
|
24
|
+
spinner: typeof clack.spinner;
|
|
25
|
+
isCancel: typeof clack.isCancel;
|
|
26
|
+
}
|
|
27
|
+
/** The real interactive UI. */
|
|
28
|
+
export declare function createClackUi(backend?: PromptBackend): Ui;
|
|
29
|
+
/**
|
|
30
|
+
* Non-interactive UI for `--yes`, `CEZ_DRY_RUN`, and unit tests. Prompts resolve
|
|
31
|
+
* to deterministic safe defaults (initial value, or the first option, or ""),
|
|
32
|
+
* logs go to the console. It never touches stdin, so it can drive the engine
|
|
33
|
+
* headless. Optional `answers` override defaults per-prompt-message.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createAutoUi(answers?: Record<string, unknown>, sink?: (m: string) => void, opts?: {
|
|
36
|
+
/**
|
|
37
|
+
* Enforce each prompt's `validate` on auto-answers (real `--yes` runs must
|
|
38
|
+
* fail closed on an unanswerable prompt). Off for CEZ_DRY_RUN previews,
|
|
39
|
+
* which must walk every step with placeholder-grade values.
|
|
40
|
+
*/
|
|
41
|
+
strictValidate?: boolean;
|
|
42
|
+
}): Ui;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as clack from '@clack/prompts';
|
|
2
|
+
import { CANCEL, PreflightError } from './types.js';
|
|
3
|
+
import { StepAborted } from './steps.js';
|
|
4
|
+
const realBackend = {
|
|
5
|
+
intro: clack.intro,
|
|
6
|
+
outro: clack.outro,
|
|
7
|
+
note: clack.note,
|
|
8
|
+
log: clack.log,
|
|
9
|
+
select: clack.select,
|
|
10
|
+
multiselect: clack.multiselect,
|
|
11
|
+
confirm: clack.confirm,
|
|
12
|
+
text: clack.text,
|
|
13
|
+
password: clack.password,
|
|
14
|
+
spinner: clack.spinner,
|
|
15
|
+
isCancel: clack.isCancel,
|
|
16
|
+
};
|
|
17
|
+
/** Map a clack result to `T | CANCEL`, never a thrown cancel. */
|
|
18
|
+
function unwrap(value, isCancel) {
|
|
19
|
+
return isCancel(value) ? CANCEL : value;
|
|
20
|
+
}
|
|
21
|
+
/** The real interactive UI. */
|
|
22
|
+
export function createClackUi(backend = realBackend) {
|
|
23
|
+
// clack renders a prompt at stdin-EOF and never resolves — a piped/ssh'd
|
|
24
|
+
// invocation would hang forever while holding the single-writer install
|
|
25
|
+
// lock. Refuse up front with the way out. (Injected fake backends are for
|
|
26
|
+
// tests, which have no TTY.)
|
|
27
|
+
if (backend === realBackend && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
28
|
+
throw new PreflightError('this terminal is not interactive — re-run from a TTY, or pass --yes (or CEZ_DRY_RUN=1) for non-interactive mode');
|
|
29
|
+
}
|
|
30
|
+
const wrapValidate = (validate) => validate ? (v) => validate(v ?? '') : undefined;
|
|
31
|
+
return {
|
|
32
|
+
intro: (m) => backend.intro(m),
|
|
33
|
+
outro: (m) => backend.outro(m),
|
|
34
|
+
note: (m, title) => backend.note(m, title),
|
|
35
|
+
// Raw, un-boxed: clack's note() draws a bordered box that breaks when a long
|
|
36
|
+
// command wraps. Write straight to stdout so the command stays selectable.
|
|
37
|
+
message: (m) => process.stdout.write(`\n${m}\n`),
|
|
38
|
+
info: (m) => backend.log.info(m),
|
|
39
|
+
success: (m) => backend.log.success(m),
|
|
40
|
+
warn: (m) => backend.log.warn(m),
|
|
41
|
+
error: (m) => backend.log.error(m),
|
|
42
|
+
async select(opts) {
|
|
43
|
+
// clack's `Option<Value>` is a conditional type that can't resolve against
|
|
44
|
+
// an unconstrained generic; cast the options and keep the explicit T.
|
|
45
|
+
const res = await backend.select({
|
|
46
|
+
message: opts.message,
|
|
47
|
+
options: opts.options,
|
|
48
|
+
initialValue: opts.initialValue,
|
|
49
|
+
});
|
|
50
|
+
return unwrap(res, backend.isCancel);
|
|
51
|
+
},
|
|
52
|
+
async multiselect(opts) {
|
|
53
|
+
const res = await backend.multiselect({
|
|
54
|
+
message: opts.message,
|
|
55
|
+
options: opts.options,
|
|
56
|
+
required: opts.required ?? false,
|
|
57
|
+
});
|
|
58
|
+
return unwrap(res, backend.isCancel);
|
|
59
|
+
},
|
|
60
|
+
async confirm(opts) {
|
|
61
|
+
return unwrap(await backend.confirm(opts), backend.isCancel);
|
|
62
|
+
},
|
|
63
|
+
async text(opts) {
|
|
64
|
+
return unwrap(await backend.text({ ...opts, validate: wrapValidate(opts.validate) }), backend.isCancel);
|
|
65
|
+
},
|
|
66
|
+
async password(opts) {
|
|
67
|
+
return unwrap(await backend.password({ ...opts, validate: wrapValidate(opts.validate) }), backend.isCancel);
|
|
68
|
+
},
|
|
69
|
+
spinner() {
|
|
70
|
+
const s = backend.spinner();
|
|
71
|
+
return {
|
|
72
|
+
start: (m) => s.start(m),
|
|
73
|
+
stop: (m) => s.stop(m),
|
|
74
|
+
message: (m) => s.message(m),
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Non-interactive UI for `--yes`, `CEZ_DRY_RUN`, and unit tests. Prompts resolve
|
|
81
|
+
* to deterministic safe defaults (initial value, or the first option, or ""),
|
|
82
|
+
* logs go to the console. It never touches stdin, so it can drive the engine
|
|
83
|
+
* headless. Optional `answers` override defaults per-prompt-message.
|
|
84
|
+
*/
|
|
85
|
+
export function createAutoUi(answers = {}, sink = () => { }, opts = {}) {
|
|
86
|
+
const answer = (message, fallback) => (message in answers ? answers[message] : fallback);
|
|
87
|
+
const checkValid = (message, v, validate) => {
|
|
88
|
+
if (!opts.strictValidate)
|
|
89
|
+
return;
|
|
90
|
+
const invalid = validate?.(v);
|
|
91
|
+
if (invalid !== undefined) {
|
|
92
|
+
throw new StepAborted(`cannot auto-answer "${message}" (${invalid}) — run without --yes to answer it`);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
intro: sink,
|
|
97
|
+
outro: sink,
|
|
98
|
+
note: (m) => sink(m),
|
|
99
|
+
message: sink,
|
|
100
|
+
info: sink,
|
|
101
|
+
success: sink,
|
|
102
|
+
warn: sink,
|
|
103
|
+
error: sink,
|
|
104
|
+
async select(opts) {
|
|
105
|
+
return answer(opts.message, opts.initialValue ?? opts.options[0]?.value);
|
|
106
|
+
},
|
|
107
|
+
async multiselect(opts) {
|
|
108
|
+
return answer(opts.message, []);
|
|
109
|
+
},
|
|
110
|
+
async confirm(opts) {
|
|
111
|
+
return answer(opts.message, opts.initialValue ?? true);
|
|
112
|
+
},
|
|
113
|
+
// A placeholder is a HINT, never an answer — auto-adopting it turned the
|
|
114
|
+
// example domain `cezar.ngrok.app` into real input under `--yes`.
|
|
115
|
+
async text(o) {
|
|
116
|
+
const v = String(answer(o.message, o.initialValue ?? ''));
|
|
117
|
+
checkValid(o.message, v, o.validate);
|
|
118
|
+
return v;
|
|
119
|
+
},
|
|
120
|
+
async password(o) {
|
|
121
|
+
const v = String(answer(o.message, ''));
|
|
122
|
+
checkValid(o.message, v, o.validate);
|
|
123
|
+
return v;
|
|
124
|
+
},
|
|
125
|
+
spinner() {
|
|
126
|
+
return { start: (m) => m && sink(m), stop: (m) => m && sink(m), message: (m) => sink(m) };
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/server-install/ui.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promotes `open-mercato/skills` (#391) — cezar's built-in default skills source
|
|
3
|
+
* (`DEFAULT_SKILLS_REPOS` in `src/config.ts`) that today only surfaces in `--help`. Split out of
|
|
4
|
+
* `src/index.ts` so the CLI banner has one source of truth the web cockpit copy
|
|
5
|
+
* (`web/app/src/components/skills-banner.tsx`) can be kept in step with, and so it is testable
|
|
6
|
+
* without importing `index.ts` (which runs `main()` on import).
|
|
7
|
+
*
|
|
8
|
+
* Printed on `serve` start — a few lines in an already-chatty startup block. It has two off
|
|
9
|
+
* switches, because a promo that cannot be silenced is a nag:
|
|
10
|
+
* - `CEZ_NO_BANNER=1` in the environment, and
|
|
11
|
+
* - `dismissedSkillsBanner` in `.ai/cezar/ui-state.json` — the SAME flag the cockpit banner
|
|
12
|
+
* persists, so dismissing it there also silences the terminal and the two surfaces tell one
|
|
13
|
+
* story.
|
|
14
|
+
* Reading that flag follows the `src/config.ts` rule: a missing/unreadable/malformed state file
|
|
15
|
+
* degrades to showing the banner, never to a crash or a blocked startup.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SKILLS_BANNER_LINES: readonly string[];
|
|
18
|
+
/** Both off switches in one place. Never throws. */
|
|
19
|
+
export declare function shouldShowSkillsBanner(repoRoot: string): Promise<boolean>;
|
|
20
|
+
/** The `serve` startup banner. `log` is injectable so the wiring is testable. */
|
|
21
|
+
export declare function printSkillsBanner(repoRoot: string, log?: (line?: string) => void): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readUiState } from './ui-state.js';
|
|
2
|
+
/**
|
|
3
|
+
* Promotes `open-mercato/skills` (#391) — cezar's built-in default skills source
|
|
4
|
+
* (`DEFAULT_SKILLS_REPOS` in `src/config.ts`) that today only surfaces in `--help`. Split out of
|
|
5
|
+
* `src/index.ts` so the CLI banner has one source of truth the web cockpit copy
|
|
6
|
+
* (`web/app/src/components/skills-banner.tsx`) can be kept in step with, and so it is testable
|
|
7
|
+
* without importing `index.ts` (which runs `main()` on import).
|
|
8
|
+
*
|
|
9
|
+
* Printed on `serve` start — a few lines in an already-chatty startup block. It has two off
|
|
10
|
+
* switches, because a promo that cannot be silenced is a nag:
|
|
11
|
+
* - `CEZ_NO_BANNER=1` in the environment, and
|
|
12
|
+
* - `dismissedSkillsBanner` in `.ai/cezar/ui-state.json` — the SAME flag the cockpit banner
|
|
13
|
+
* persists, so dismissing it there also silences the terminal and the two surfaces tell one
|
|
14
|
+
* story.
|
|
15
|
+
* Reading that flag follows the `src/config.ts` rule: a missing/unreadable/malformed state file
|
|
16
|
+
* degrades to showing the banner, never to a crash or a blocked startup.
|
|
17
|
+
*/
|
|
18
|
+
export const SKILLS_BANNER_LINES = [
|
|
19
|
+
' 🤖 Make the most of parallel coding with our AI skills',
|
|
20
|
+
' open-mercato/skills: reusable, technology-agnostic agent skills for PR',
|
|
21
|
+
' creation, code review, CI stabilisation, spec writing & more.',
|
|
22
|
+
' cezar already loads them for you — Skills → Refresh in the cockpit gets the latest.',
|
|
23
|
+
" To use them outside cezar: npx skills add open-mercato/skills --skill '*'",
|
|
24
|
+
];
|
|
25
|
+
/** Both off switches in one place. Never throws. */
|
|
26
|
+
export async function shouldShowSkillsBanner(repoRoot) {
|
|
27
|
+
if (process.env.CEZ_NO_BANNER === '1')
|
|
28
|
+
return false;
|
|
29
|
+
const uiState = await readUiState(repoRoot);
|
|
30
|
+
return uiState.dismissedSkillsBanner !== true;
|
|
31
|
+
}
|
|
32
|
+
/** The `serve` startup banner. `log` is injectable so the wiring is testable. */
|
|
33
|
+
export async function printSkillsBanner(repoRoot, log = console.log) {
|
|
34
|
+
if (!(await shouldShowSkillsBanner(repoRoot)))
|
|
35
|
+
return;
|
|
36
|
+
for (const line of SKILLS_BANNER_LINES)
|
|
37
|
+
log(line);
|
|
38
|
+
log();
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=skills-banner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills-banner.js","sourceRoot":"","sources":["../src/skills-banner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAsB;IACpD,0DAA0D;IAC1D,0EAA0E;IAC1E,iEAAiE;IACjE,uFAAuF;IACvF,8EAA8E;CAC/E,CAAC;AAEF,oDAAoD;AACpD,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,QAAgB;IAC3D,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,qBAAqB,KAAK,IAAI,CAAC;AAChD,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,MAA+B,OAAO,CAAC,GAAG;IAE1C,IAAI,CAAC,CAAC,MAAM,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO;IACtD,KAAK,MAAM,IAAI,IAAI,mBAAmB;QAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IAClD,GAAG,EAAE,CAAC;AACR,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type SkillsRepoSource } from './config.js';
|
|
2
|
+
import { type Skill } from './skills.js';
|
|
3
|
+
/** Stable cache directory name: the last two path segments, `owner__name`. */
|
|
4
|
+
export declare function bareDirFor(repo: string): string;
|
|
5
|
+
/** Clone the skills repo bare (no checkout) into the global cache, once. */
|
|
6
|
+
export declare function ensureBareClone(repo: string): Promise<{
|
|
7
|
+
bareDir: string;
|
|
8
|
+
created: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
/** "Refresh" — update every branch head in the bare clone from origin. */
|
|
11
|
+
export declare function fetchAll(bareDir: string): Promise<void>;
|
|
12
|
+
/** Read one file from the bare clone at the source's ref. Null on any failure. */
|
|
13
|
+
export declare function readRemoteSkill(src: SkillsRepoSource, path: string): Promise<string | null>;
|
|
14
|
+
/**
|
|
15
|
+
* List every skill the repo defines at `src.ref`. Reads from the local bare
|
|
16
|
+
* clone only — no network. Empty list when the clone doesn't exist yet or
|
|
17
|
+
* the ref can't be resolved.
|
|
18
|
+
*/
|
|
19
|
+
export declare function listRemoteSkills(src: SkillsRepoSource): Promise<Skill[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Copy a directory skill (SKILL.md + references/…) out of the bare clone into
|
|
22
|
+
* `<repoRoot>/.claude/skills/<name>/` so claude sees the references on disk,
|
|
23
|
+
* and keep it out of the user's git via `.git/info/exclude`. Returns false
|
|
24
|
+
* when there is nothing to materialize (not a directory skill, no clone…).
|
|
25
|
+
*/
|
|
26
|
+
export declare function materializeSkillDir(repoRoot: string, skill: Skill): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* The current team-skill list, straight from memory. The first call kicks off
|
|
29
|
+
* an async background load (clone + list) and returns immediately — the GUI
|
|
30
|
+
* refetches, so remote skills appear moments later instead of blocking the
|
|
31
|
+
* first `GET /api/skills`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getTeamSkillsCached(repoRoot: string): Skill[];
|
|
34
|
+
/** Refresh: clone missing sources, `git fetch` existing ones, reload the list. */
|
|
35
|
+
export declare function refreshTeamSkills(repoRoot: string): Promise<Skill[]>;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { basename, dirname, join } from 'node:path';
|
|
6
|
+
import { loadConfig } from './config.js';
|
|
7
|
+
import { parseFrontmatter } from './skills.js';
|
|
8
|
+
/**
|
|
9
|
+
* Team skills from remote git repos (spec 005), janitor-style: a bare clone
|
|
10
|
+
* without a checkout, listed with `git ls-tree` and read with `git show`.
|
|
11
|
+
* The cache lives in `~/.cache/cez/skills/<owner>__<name>/` — global, so one
|
|
12
|
+
* fetch serves every project. Everything degrades: no network / no access to
|
|
13
|
+
* the skills repo means the team skills quietly disappear from the list while
|
|
14
|
+
* local skills keep working. Nothing here ever blocks startup.
|
|
15
|
+
*/
|
|
16
|
+
const LIST_TIMEOUT_MS = 10_000; // ls-tree / show / rev-parse
|
|
17
|
+
const CLONE_TIMEOUT_MS = 60_000; // clone / fetch
|
|
18
|
+
function git(args, timeoutMs, cwd) {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
execFile('git', args, { cwd, timeout: timeoutMs, killSignal: 'SIGKILL', maxBuffer: 16 * 1024 * 1024, encoding: 'utf8' }, (err, stdout, stderr) => resolve({ ok: !err, stdout: stdout ?? '', stderr: stderr ?? '' }));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** `owner/name` shorthand → GitHub HTTPS; anything else (URL, file://, local path) as-is. */
|
|
24
|
+
function remoteFor(repo) {
|
|
25
|
+
return /^[\w.-]+\/[\w.-]+$/.test(repo) ? `https://github.com/${repo}.git` : repo;
|
|
26
|
+
}
|
|
27
|
+
/** Stable cache directory name: the last two path segments, `owner__name`. */
|
|
28
|
+
export function bareDirFor(repo) {
|
|
29
|
+
const trimmed = repo
|
|
30
|
+
.replace(/\/+$/, '')
|
|
31
|
+
.replace(/\.git$/, '')
|
|
32
|
+
.replace(/^[a-z+]+:\/\//i, '')
|
|
33
|
+
.replace(/^~\//, '');
|
|
34
|
+
const segments = trimmed.split(/[/:]/).filter(Boolean).map(sanitizeSegment);
|
|
35
|
+
const key = segments.slice(-2).join('__') || 'skills';
|
|
36
|
+
return join(homedir(), '.cache', 'cez', 'skills', key);
|
|
37
|
+
}
|
|
38
|
+
function sanitizeSegment(s) {
|
|
39
|
+
return s.replace(/[^\w.-]/g, '-');
|
|
40
|
+
}
|
|
41
|
+
/** Clone the skills repo bare (no checkout) into the global cache, once. */
|
|
42
|
+
export async function ensureBareClone(repo) {
|
|
43
|
+
const bareDir = bareDirFor(repo);
|
|
44
|
+
if (existsSync(join(bareDir, 'HEAD')))
|
|
45
|
+
return { bareDir, created: false };
|
|
46
|
+
await mkdir(dirname(bareDir), { recursive: true });
|
|
47
|
+
const remote = remoteFor(repo);
|
|
48
|
+
const res = await git(['clone', '--bare', remote, bareDir], CLONE_TIMEOUT_MS);
|
|
49
|
+
if (!res.ok)
|
|
50
|
+
throw new Error(`git clone --bare ${remote} failed: ${res.stderr.trim()}`);
|
|
51
|
+
return { bareDir, created: true };
|
|
52
|
+
}
|
|
53
|
+
/** "Refresh" — update every branch head in the bare clone from origin. */
|
|
54
|
+
export async function fetchAll(bareDir) {
|
|
55
|
+
const res = await git(['fetch', 'origin', '--prune', '+refs/heads/*:refs/heads/*'], CLONE_TIMEOUT_MS, bareDir);
|
|
56
|
+
if (!res.ok)
|
|
57
|
+
throw new Error(`git fetch failed: ${res.stderr.trim() || res.stdout.trim()}`);
|
|
58
|
+
}
|
|
59
|
+
async function resolveRef(bareDir, ref) {
|
|
60
|
+
for (const candidate of [ref, `refs/heads/${ref}`, 'HEAD']) {
|
|
61
|
+
const probe = await git(['rev-parse', '--verify', '--quiet', candidate], LIST_TIMEOUT_MS, bareDir);
|
|
62
|
+
if (probe.ok)
|
|
63
|
+
return candidate;
|
|
64
|
+
}
|
|
65
|
+
return ref;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Match the janitor conventions plus our own:
|
|
69
|
+
* - `**\/SKILL.md` → skill named after the parent directory (with references/)
|
|
70
|
+
* - `**\/commands/<n>.md` → skill `<n>`
|
|
71
|
+
* - `.ai/skills/**\/*.md` or `.ai/cezar/skills/**\/*.md` → frontmatter/basename name
|
|
72
|
+
*/
|
|
73
|
+
function matchSkillPath(line) {
|
|
74
|
+
if (line === 'SKILL.md' || line.endsWith('/SKILL.md')) {
|
|
75
|
+
const parts = line.split('/');
|
|
76
|
+
if (parts.length < 2)
|
|
77
|
+
return null;
|
|
78
|
+
const parent = parts[parts.length - 2];
|
|
79
|
+
return parent && parent !== '.' ? { name: parent, kind: 'skill' } : null;
|
|
80
|
+
}
|
|
81
|
+
const cmd = /(?:^|\/)commands\/([^/]+)\.md$/.exec(line);
|
|
82
|
+
if (cmd)
|
|
83
|
+
return { name: cmd[1], kind: 'command' };
|
|
84
|
+
if (/(?:^|\/)\.ai\/(?:cezar\/)?skills\/.+\.md$/.test(line))
|
|
85
|
+
return { name: null, kind: 'markdown' };
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
/** Read one file from the bare clone at the source's ref. Null on any failure. */
|
|
89
|
+
export async function readRemoteSkill(src, path) {
|
|
90
|
+
const bareDir = bareDirFor(src.repo);
|
|
91
|
+
if (!existsSync(join(bareDir, 'HEAD')))
|
|
92
|
+
return null;
|
|
93
|
+
const ref = await resolveRef(bareDir, src.ref);
|
|
94
|
+
const res = await git(['show', `${ref}:${path}`], LIST_TIMEOUT_MS, bareDir);
|
|
95
|
+
return res.ok ? res.stdout : null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* List every skill the repo defines at `src.ref`. Reads from the local bare
|
|
99
|
+
* clone only — no network. Empty list when the clone doesn't exist yet or
|
|
100
|
+
* the ref can't be resolved.
|
|
101
|
+
*/
|
|
102
|
+
export async function listRemoteSkills(src) {
|
|
103
|
+
const bareDir = bareDirFor(src.repo);
|
|
104
|
+
if (!existsSync(join(bareDir, 'HEAD')))
|
|
105
|
+
return [];
|
|
106
|
+
const ref = await resolveRef(bareDir, src.ref);
|
|
107
|
+
const ls = await git(['ls-tree', '-r', '--name-only', ref], LIST_TIMEOUT_MS, bareDir);
|
|
108
|
+
if (!ls.ok)
|
|
109
|
+
return [];
|
|
110
|
+
const skills = [];
|
|
111
|
+
const seen = new Set();
|
|
112
|
+
for (const line of ls.stdout.split('\n')) {
|
|
113
|
+
if (!line)
|
|
114
|
+
continue;
|
|
115
|
+
const hit = matchSkillPath(line);
|
|
116
|
+
if (!hit)
|
|
117
|
+
continue;
|
|
118
|
+
const raw = await readRemoteSkill(src, line);
|
|
119
|
+
if (raw === null)
|
|
120
|
+
continue;
|
|
121
|
+
const { frontmatter, body } = parseFrontmatter(raw);
|
|
122
|
+
const name = hit.name ??
|
|
123
|
+
(typeof frontmatter.name === 'string' && frontmatter.name.trim()
|
|
124
|
+
? frontmatter.name.trim()
|
|
125
|
+
: basename(line, '.md'));
|
|
126
|
+
if (seen.has(name))
|
|
127
|
+
continue;
|
|
128
|
+
seen.add(name);
|
|
129
|
+
const description = typeof frontmatter.description === 'string' && frontmatter.description.trim()
|
|
130
|
+
? frontmatter.description.trim()
|
|
131
|
+
: undefined;
|
|
132
|
+
skills.push({
|
|
133
|
+
name,
|
|
134
|
+
description,
|
|
135
|
+
body,
|
|
136
|
+
path: `${src.repo}@${src.ref}:${line}`,
|
|
137
|
+
source: 'team',
|
|
138
|
+
team: { repo: src.repo, ref: src.ref, path: line, dir: hit.kind === 'skill' },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return skills;
|
|
142
|
+
}
|
|
143
|
+
// ---- materialization (directory skills) ---------------------------------------
|
|
144
|
+
/**
|
|
145
|
+
* Copy a directory skill (SKILL.md + references/…) out of the bare clone into
|
|
146
|
+
* `<repoRoot>/.claude/skills/<name>/` so claude sees the references on disk,
|
|
147
|
+
* and keep it out of the user's git via `.git/info/exclude`. Returns false
|
|
148
|
+
* when there is nothing to materialize (not a directory skill, no clone…).
|
|
149
|
+
*/
|
|
150
|
+
export async function materializeSkillDir(repoRoot, skill) {
|
|
151
|
+
if (!skill.team?.dir || !skill.team.path.endsWith('SKILL.md'))
|
|
152
|
+
return false;
|
|
153
|
+
const bareDir = bareDirFor(skill.team.repo);
|
|
154
|
+
if (!existsSync(join(bareDir, 'HEAD')))
|
|
155
|
+
return false;
|
|
156
|
+
const ref = await resolveRef(bareDir, skill.team.ref);
|
|
157
|
+
const srcDir = skill.team.path.slice(0, -'/SKILL.md'.length);
|
|
158
|
+
const ls = await git(['ls-tree', '-r', '--name-only', ref, '--', srcDir], LIST_TIMEOUT_MS, bareDir);
|
|
159
|
+
if (!ls.ok)
|
|
160
|
+
return false;
|
|
161
|
+
const destDir = join(repoRoot, '.claude', 'skills', skill.name);
|
|
162
|
+
let wrote = 0;
|
|
163
|
+
for (const file of ls.stdout.split('\n').filter(Boolean)) {
|
|
164
|
+
const rel = file.slice(srcDir.length + 1);
|
|
165
|
+
// git paths are repo-relative and normalized, but never trust them blindly.
|
|
166
|
+
if (!rel || rel.split('/').includes('..'))
|
|
167
|
+
continue;
|
|
168
|
+
const show = await git(['show', `${ref}:${file}`], LIST_TIMEOUT_MS, bareDir);
|
|
169
|
+
if (!show.ok)
|
|
170
|
+
continue;
|
|
171
|
+
const target = join(destDir, rel);
|
|
172
|
+
await mkdir(dirname(target), { recursive: true });
|
|
173
|
+
await writeFile(target, show.stdout, 'utf8');
|
|
174
|
+
wrote++;
|
|
175
|
+
}
|
|
176
|
+
if (wrote === 0)
|
|
177
|
+
return false;
|
|
178
|
+
await excludeFromGit(repoRoot, `.claude/skills/${skill.name}/`);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
/** Append a pattern to git's `info/exclude` (idempotent, non-fatal). */
|
|
182
|
+
async function excludeFromGit(repoRoot, pattern) {
|
|
183
|
+
try {
|
|
184
|
+
// Resolve the real exclude file: in a linked worktree (spec 006) `.git`
|
|
185
|
+
// is a file and `info/exclude` lives in the shared common dir — which
|
|
186
|
+
// also means one exclude entry covers every task worktree.
|
|
187
|
+
const probe = await git(['rev-parse', '--path-format=absolute', '--git-common-dir'], LIST_TIMEOUT_MS, repoRoot);
|
|
188
|
+
const gitDir = probe.ok && probe.stdout.trim() ? probe.stdout.trim() : join(repoRoot, '.git');
|
|
189
|
+
const excludePath = join(gitDir, 'info', 'exclude');
|
|
190
|
+
let prev = '';
|
|
191
|
+
try {
|
|
192
|
+
prev = await readFile(excludePath, 'utf8');
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// no exclude file yet
|
|
196
|
+
}
|
|
197
|
+
if (prev.split('\n').includes(pattern))
|
|
198
|
+
return;
|
|
199
|
+
await mkdir(dirname(excludePath), { recursive: true });
|
|
200
|
+
await writeFile(excludePath, prev + (prev && !prev.endsWith('\n') ? '\n' : '') + pattern + '\n', 'utf8');
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// non-fatal — `.git` might be a linked file (worktree) or absent entirely
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// ---- in-process cache ----------------------------------------------------------
|
|
207
|
+
// Clone attempts are expensive when the network is down (git can hang on
|
|
208
|
+
// DNS/TCP), so each source gets one implicit attempt per process. "Refresh"
|
|
209
|
+
// always retries.
|
|
210
|
+
const cloneAttempted = new Set();
|
|
211
|
+
let teamSkills = [];
|
|
212
|
+
let firstLoadStarted = false;
|
|
213
|
+
/**
|
|
214
|
+
* The current team-skill list, straight from memory. The first call kicks off
|
|
215
|
+
* an async background load (clone + list) and returns immediately — the GUI
|
|
216
|
+
* refetches, so remote skills appear moments later instead of blocking the
|
|
217
|
+
* first `GET /api/skills`.
|
|
218
|
+
*/
|
|
219
|
+
export function getTeamSkillsCached(repoRoot) {
|
|
220
|
+
if (!firstLoadStarted) {
|
|
221
|
+
firstLoadStarted = true;
|
|
222
|
+
void loadTeamSkills(repoRoot, false).catch(() => undefined);
|
|
223
|
+
}
|
|
224
|
+
return teamSkills;
|
|
225
|
+
}
|
|
226
|
+
/** Refresh: clone missing sources, `git fetch` existing ones, reload the list. */
|
|
227
|
+
export async function refreshTeamSkills(repoRoot) {
|
|
228
|
+
firstLoadStarted = true;
|
|
229
|
+
return loadTeamSkills(repoRoot, true);
|
|
230
|
+
}
|
|
231
|
+
async function loadTeamSkills(repoRoot, refresh) {
|
|
232
|
+
const config = await loadConfig(repoRoot);
|
|
233
|
+
const out = [];
|
|
234
|
+
const seen = new Set();
|
|
235
|
+
for (const src of config.skillsRepos) {
|
|
236
|
+
try {
|
|
237
|
+
if (refresh) {
|
|
238
|
+
const { bareDir, created } = await ensureBareClone(src.repo);
|
|
239
|
+
if (!created)
|
|
240
|
+
await fetchAll(bareDir);
|
|
241
|
+
cloneAttempted.add(src.repo);
|
|
242
|
+
}
|
|
243
|
+
else if (!cloneAttempted.has(src.repo)) {
|
|
244
|
+
cloneAttempted.add(src.repo);
|
|
245
|
+
await ensureBareClone(src.repo);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// offline / no access — list whatever an older clone has (or nothing)
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
for (const skill of await listRemoteSkills(src)) {
|
|
253
|
+
if (seen.has(skill.name))
|
|
254
|
+
continue;
|
|
255
|
+
seen.add(skill.name);
|
|
256
|
+
out.push(skill);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// degrade: this source contributes nothing
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
teamSkills = out;
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=skills-remote.js.map
|