@oneworks/cli 0.1.0-alpha.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/LICENSE +21 -0
- package/channel.js +7 -0
- package/cli.js +5 -0
- package/mem.js +7 -0
- package/package.json +59 -0
- package/postinstall.js +75 -0
- package/src/AGENTS.md +169 -0
- package/src/channel-cli.ts +19 -0
- package/src/cli-argv.ts +27 -0
- package/src/cli.ts +63 -0
- package/src/commands/@core/adapter-option.ts +85 -0
- package/src/commands/@core/extra-options.ts +12 -0
- package/src/commands/@core/plugin-install.ts +1 -0
- package/src/commands/@core/plugin-source.ts +1 -0
- package/src/commands/accounts.ts +204 -0
- package/src/commands/adapter/prepare-selection.ts +181 -0
- package/src/commands/adapter/prepare.ts +104 -0
- package/src/commands/adapter.ts +48 -0
- package/src/commands/agent/actions.ts +176 -0
- package/src/commands/agent/runtime-store-commands.ts +56 -0
- package/src/commands/agent/runtime-store-events.ts +23 -0
- package/src/commands/agent/runtime-store-session.ts +170 -0
- package/src/commands/agent/runtime-store-shared.ts +139 -0
- package/src/commands/agent/runtime-store.ts +4 -0
- package/src/commands/agent.ts +81 -0
- package/src/commands/benchmark.ts +198 -0
- package/src/commands/channel.ts +594 -0
- package/src/commands/clear.ts +140 -0
- package/src/commands/config/actions.ts +196 -0
- package/src/commands/config/display-state.ts +108 -0
- package/src/commands/config/index.ts +135 -0
- package/src/commands/config/interactive.ts +121 -0
- package/src/commands/config/read-state.ts +56 -0
- package/src/commands/config/section-state.ts +109 -0
- package/src/commands/config/shared.ts +195 -0
- package/src/commands/kill.ts +41 -0
- package/src/commands/list.ts +224 -0
- package/src/commands/memory/context.ts +76 -0
- package/src/commands/memory/entries.ts +131 -0
- package/src/commands/memory/shared.ts +89 -0
- package/src/commands/memory/store.ts +69 -0
- package/src/commands/memory/target.ts +54 -0
- package/src/commands/memory.ts +97 -0
- package/src/commands/plugin.ts +62 -0
- package/src/commands/report-targets.ts +149 -0
- package/src/commands/report.ts +232 -0
- package/src/commands/run/adapter-cli-version.ts +65 -0
- package/src/commands/run/command.ts +982 -0
- package/src/commands/run/input-bridge.ts +108 -0
- package/src/commands/run/input-control.ts +112 -0
- package/src/commands/run/input-decision.ts +88 -0
- package/src/commands/run/options.ts +104 -0
- package/src/commands/run/output.ts +179 -0
- package/src/commands/run/permission-decision.ts +19 -0
- package/src/commands/run/permission-recovery.ts +194 -0
- package/src/commands/run/permission-state.ts +177 -0
- package/src/commands/run/print-idle-timeout.ts +47 -0
- package/src/commands/run/protocol-envelope.ts +111 -0
- package/src/commands/run/protocol-stdio.ts +71 -0
- package/src/commands/run/protocol.ts +391 -0
- package/src/commands/run/runtime-command-bridge.ts +190 -0
- package/src/commands/run/runtime-event-sink.ts +560 -0
- package/src/commands/run/session-exit-controller.ts +45 -0
- package/src/commands/run/types.ts +65 -0
- package/src/commands/run.ts +62 -0
- package/src/commands/session-control.ts +133 -0
- package/src/commands/skills/add-command.ts +88 -0
- package/src/commands/skills/install-command.ts +105 -0
- package/src/commands/skills/install.ts +216 -0
- package/src/commands/skills/progress.ts +126 -0
- package/src/commands/skills/publish-command.ts +85 -0
- package/src/commands/skills/register.ts +17 -0
- package/src/commands/skills/remove-command.ts +102 -0
- package/src/commands/skills/shared.ts +117 -0
- package/src/commands/skills/sync.ts +571 -0
- package/src/commands/skills/types.ts +33 -0
- package/src/commands/skills.ts +1 -0
- package/src/commands/stop.ts +41 -0
- package/src/config.ts +1 -0
- package/src/default-skill-plugin.ts +29 -0
- package/src/env.ts +1 -0
- package/src/hooks/plugins/index.ts +66 -0
- package/src/mem-cli.ts +19 -0
- package/src/session-cache.ts +250 -0
- package/src/session-permission-cache.ts +40 -0
- package/src/utils.ts +25 -0
- package/src/workspace.ts +12 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/* eslint-disable max-lines -- skill sync coordinates install planning, lockfile updates, and cleanup. */
|
|
2
|
+
import { readdir, rm, rmdir } from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
|
|
6
|
+
import type { ConfiguredSkillInstallConfig, WorkspaceAsset } from '@oneworks/types'
|
|
7
|
+
import {
|
|
8
|
+
assertSkillDirectoryUnchanged,
|
|
9
|
+
buildProjectSkillLockEntry,
|
|
10
|
+
installProjectSkill,
|
|
11
|
+
installProjectSkillCollection,
|
|
12
|
+
isConfiguredSkillCollectionInstall,
|
|
13
|
+
isWildcardSkillInclude,
|
|
14
|
+
normalizeProjectSkillInstall,
|
|
15
|
+
readProjectSkillDependencies,
|
|
16
|
+
readProjectSkillsLockfile,
|
|
17
|
+
resolveProjectOoPath,
|
|
18
|
+
toSkillSlug,
|
|
19
|
+
writeProjectSkillsLockfile
|
|
20
|
+
} from '@oneworks/utils'
|
|
21
|
+
import type {
|
|
22
|
+
NormalizedProjectSkillDependency,
|
|
23
|
+
NormalizedProjectSkillInstall,
|
|
24
|
+
ProjectSkillLockConstraint
|
|
25
|
+
} from '@oneworks/utils'
|
|
26
|
+
import { resolveWorkspaceAssetBundle } from '@oneworks/workspace-assets'
|
|
27
|
+
|
|
28
|
+
import type { ResolvedSkillInstallTarget } from './install'
|
|
29
|
+
import type { SkillsProgressReporter } from './progress'
|
|
30
|
+
import type { loadSkillsConfigState } from './shared'
|
|
31
|
+
|
|
32
|
+
type SkillAsset = Extract<WorkspaceAsset, { kind: 'skill' }>
|
|
33
|
+
type SkillsConfigState = Awaited<ReturnType<typeof loadSkillsConfigState>>
|
|
34
|
+
type SyncTarget = string | ConfiguredSkillInstallConfig | ResolvedSkillInstallTarget
|
|
35
|
+
|
|
36
|
+
interface ScopeState {
|
|
37
|
+
installPathSegments: string[]
|
|
38
|
+
kind: 'project' | 'plugin'
|
|
39
|
+
pluginInstance?: string
|
|
40
|
+
pluginInstancePath?: string
|
|
41
|
+
pluginRootSeen?: Set<string>
|
|
42
|
+
seen: Map<string, SeenSkill>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface SeenSkill {
|
|
46
|
+
constraints: ProjectSkillLockConstraint[]
|
|
47
|
+
dependencyOf: string[]
|
|
48
|
+
normalized: NormalizedProjectSkillInstall
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface SyncSkillParams {
|
|
52
|
+
constraint?: ProjectSkillLockConstraint
|
|
53
|
+
dependencyOf?: string
|
|
54
|
+
installedResult?: {
|
|
55
|
+
dirName: string
|
|
56
|
+
hash: string
|
|
57
|
+
installDir: string
|
|
58
|
+
name: string
|
|
59
|
+
ref: string
|
|
60
|
+
skillPath: string
|
|
61
|
+
}
|
|
62
|
+
normalized: NormalizedProjectSkillInstall
|
|
63
|
+
requested: boolean
|
|
64
|
+
scope: ScopeState
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const toUniqueStrings = (values: string[]) => Array.from(new Set(values.filter(value => value.trim() !== '')))
|
|
68
|
+
|
|
69
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => (
|
|
70
|
+
value != null && typeof value === 'object' && !Array.isArray(value)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const isResolvedSkillInstallTarget = (value: SyncTarget): value is ResolvedSkillInstallTarget => (
|
|
74
|
+
isRecord(value) && 'declaration' in value
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const normalizeSyncTarget = (value: SyncTarget): ResolvedSkillInstallTarget => (
|
|
78
|
+
isResolvedSkillInstallTarget(value)
|
|
79
|
+
? value
|
|
80
|
+
: {
|
|
81
|
+
declaration: value,
|
|
82
|
+
installPathSegments: []
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const toInstallPathSegment = (value: string, fallback: string) => {
|
|
87
|
+
const segment = toSkillSlug(value).replace(/:/g, '-')
|
|
88
|
+
return segment === '' ? fallback : segment
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const resolveCollectionInstallPathSegments = (
|
|
92
|
+
target: ConfiguredSkillInstallConfig,
|
|
93
|
+
baseSegments: string[]
|
|
94
|
+
) => (
|
|
95
|
+
isConfiguredSkillCollectionInstall(target)
|
|
96
|
+
? [...baseSegments, toInstallPathSegment(target.source, 'source')]
|
|
97
|
+
: baseSegments
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const toPluginInstanceKey = (asset: SkillAsset) => {
|
|
101
|
+
const raw = asset.scope ?? asset.instancePath ?? asset.packageId ?? asset.displayName
|
|
102
|
+
const slug = toSkillSlug(raw)
|
|
103
|
+
return slug === '' ? 'plugin' : slug
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const normalizeDependencyInstall = (dependency: NormalizedProjectSkillDependency) => (
|
|
107
|
+
normalizeProjectSkillInstall({
|
|
108
|
+
name: dependency.name,
|
|
109
|
+
...(dependency.registry == null ? {} : { registry: dependency.registry }),
|
|
110
|
+
...(dependency.source == null ? {} : { source: dependency.source }),
|
|
111
|
+
...(dependency.version == null ? {} : { version: dependency.version })
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const compareVersions = (left: string, right: string) => {
|
|
116
|
+
const leftParts = left.split('.').map(part => Number.parseInt(part, 10))
|
|
117
|
+
const rightParts = right.split('.').map(part => Number.parseInt(part, 10))
|
|
118
|
+
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index++) {
|
|
119
|
+
const leftPart = Number.isFinite(leftParts[index]) ? leftParts[index]! : 0
|
|
120
|
+
const rightPart = Number.isFinite(rightParts[index]) ? rightParts[index]! : 0
|
|
121
|
+
if (leftPart !== rightPart) return leftPart - rightPart
|
|
122
|
+
}
|
|
123
|
+
return 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const normalizeExactVersion = (value: string | undefined) => {
|
|
127
|
+
if (value == null) return undefined
|
|
128
|
+
const trimmed = value.trim()
|
|
129
|
+
return /^\d+\.\d+\.\d+(?:[-+].*)?$/.test(trimmed) ? trimmed : undefined
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const rangeAllowsExact = (range: string, exact: string) => {
|
|
133
|
+
const trimmed = range.trim()
|
|
134
|
+
if (trimmed === exact) return true
|
|
135
|
+
if (trimmed.startsWith('^')) {
|
|
136
|
+
const base = normalizeExactVersion(trimmed.slice(1))
|
|
137
|
+
if (base == null) return false
|
|
138
|
+
return exact.split('.')[0] === base.split('.')[0] && compareVersions(exact, base) >= 0
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const match = /^>=\s*(\d+\.\d+\.\d+)\s+<\s*(\d+\.\d+\.\d+)$/.exec(trimmed)
|
|
142
|
+
if (match != null) {
|
|
143
|
+
return compareVersions(exact, match[1]!) >= 0 && compareVersions(exact, match[2]!) < 0
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const assertVersionConstraintsCompatible = (params: {
|
|
150
|
+
constraints: ProjectSkillLockConstraint[]
|
|
151
|
+
name: string
|
|
152
|
+
}) => {
|
|
153
|
+
const versions = toUniqueStrings(params.constraints.map(constraint => constraint.version ?? ''))
|
|
154
|
+
if (versions.length <= 1) return
|
|
155
|
+
|
|
156
|
+
const exactVersions = versions.map(normalizeExactVersion).filter((value): value is string => value != null)
|
|
157
|
+
const uniqueExactVersions = toUniqueStrings(exactVersions)
|
|
158
|
+
if (uniqueExactVersions.length > 1) {
|
|
159
|
+
throw new Error(`Conflicting dependency versions for ${params.name}: ${versions.join(', ')}`)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (uniqueExactVersions.length === 1) {
|
|
163
|
+
const exact = uniqueExactVersions[0]!
|
|
164
|
+
const incompatible = versions.filter(version =>
|
|
165
|
+
normalizeExactVersion(version) == null && !rangeAllowsExact(version, exact)
|
|
166
|
+
)
|
|
167
|
+
if (incompatible.length > 0) {
|
|
168
|
+
throw new Error(`Conflicting dependency versions for ${params.name}: ${versions.join(', ')}`)
|
|
169
|
+
}
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw new Error(`Conflicting dependency version ranges for ${params.name}: ${versions.join(', ')}`)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const assertSameSource = (left: NormalizedProjectSkillInstall, right: NormalizedProjectSkillInstall) => {
|
|
177
|
+
if (
|
|
178
|
+
left.name !== right.name ||
|
|
179
|
+
(left.source ?? '') !== (right.source ?? '') ||
|
|
180
|
+
(left.registry ?? '') !== (right.registry ?? '')
|
|
181
|
+
) {
|
|
182
|
+
throw new Error(`Conflicting dependency sources for ${left.targetName}.`)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const findPluginSkillDependency = (
|
|
187
|
+
pluginSkills: SkillAsset[],
|
|
188
|
+
scope: ScopeState,
|
|
189
|
+
dependency: NormalizedProjectSkillDependency
|
|
190
|
+
) => {
|
|
191
|
+
if (scope.kind !== 'plugin') return undefined
|
|
192
|
+
const slug = toSkillSlug(dependency.name)
|
|
193
|
+
return pluginSkills.find(asset => (
|
|
194
|
+
asset.instancePath === scope.pluginInstancePath &&
|
|
195
|
+
(asset.name === dependency.name || toSkillSlug(asset.name) === slug)
|
|
196
|
+
))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const readDependenciesOrEmpty = async (skillPath: string) => {
|
|
200
|
+
try {
|
|
201
|
+
return await readProjectSkillDependencies(skillPath)
|
|
202
|
+
} catch {
|
|
203
|
+
return []
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const formatVersionedRef = (value: {
|
|
208
|
+
source: string
|
|
209
|
+
version?: string
|
|
210
|
+
}) => (
|
|
211
|
+
value.version == null || value.version.trim() === ''
|
|
212
|
+
? value.source
|
|
213
|
+
: `${value.source}@${value.version}`
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
const formatCollectionIncludes = (target: ConfiguredSkillInstallConfig) => {
|
|
217
|
+
if (!isConfiguredSkillCollectionInstall(target)) return undefined
|
|
218
|
+
if (target.include == null || target.include.length === 0 || target.include.some(isWildcardSkillInclude)) {
|
|
219
|
+
return undefined
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return target.include
|
|
223
|
+
.map(include => typeof include === 'string' ? include : (include.rename ?? include.name))
|
|
224
|
+
.filter(value => value.trim() !== '')
|
|
225
|
+
.join(', ')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const formatCollectionProgressLabel = (target: ConfiguredSkillInstallConfig) => {
|
|
229
|
+
if (!isConfiguredSkillCollectionInstall(target)) return 'source'
|
|
230
|
+
const source = formatVersionedRef(target)
|
|
231
|
+
const includes = formatCollectionIncludes(target)
|
|
232
|
+
return includes == null || includes === '' ? `source ${source}` : `source ${source} (${includes})`
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const formatSkillProgressLabel = (params: SyncSkillParams) => {
|
|
236
|
+
if (params.scope.kind === 'plugin') return `plugin dependency ${params.normalized.targetName}`
|
|
237
|
+
return params.requested ? `skill ${params.normalized.targetName}` : `dependency ${params.normalized.targetName}`
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const normalizeInstallPath = (installPath: string) => path.resolve(installPath)
|
|
241
|
+
|
|
242
|
+
const pruneEmptyAncestorDirs = async (params: {
|
|
243
|
+
startDir: string
|
|
244
|
+
stopDir: string
|
|
245
|
+
}) => {
|
|
246
|
+
const stopDir = normalizeInstallPath(params.stopDir)
|
|
247
|
+
let currentDir = path.dirname(normalizeInstallPath(params.startDir))
|
|
248
|
+
while (currentDir.startsWith(`${stopDir}${path.sep}`) && currentDir !== stopDir) {
|
|
249
|
+
try {
|
|
250
|
+
await rmdir(currentDir)
|
|
251
|
+
} catch {
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
currentDir = path.dirname(currentDir)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const pruneEmptyDirectoryTree = async (dir: string): Promise<void> => {
|
|
259
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => undefined)
|
|
260
|
+
if (entries == null) return
|
|
261
|
+
|
|
262
|
+
for (const entry of entries) {
|
|
263
|
+
if (!entry.isDirectory()) continue
|
|
264
|
+
await pruneEmptyDirectoryTree(path.join(dir, entry.name))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
await rmdir(dir).catch(() => undefined)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export const syncProjectSkills = async (params: {
|
|
271
|
+
force?: boolean
|
|
272
|
+
progress?: SkillsProgressReporter
|
|
273
|
+
registry?: string
|
|
274
|
+
state: SkillsConfigState
|
|
275
|
+
targets: SyncTarget[]
|
|
276
|
+
workspaceFolder: string
|
|
277
|
+
}) => {
|
|
278
|
+
const lockfile = await readProjectSkillsLockfile(params.workspaceFolder)
|
|
279
|
+
const nextLockfile = {
|
|
280
|
+
version: 1 as const,
|
|
281
|
+
...(lockfile.skills == null ? {} : { skills: { ...lockfile.skills } }),
|
|
282
|
+
...(lockfile.pluginSkills == null ? {} : { pluginSkills: { ...lockfile.pluginSkills } })
|
|
283
|
+
}
|
|
284
|
+
const installed: Array<{
|
|
285
|
+
dirName: string
|
|
286
|
+
hash: string
|
|
287
|
+
installDir: string
|
|
288
|
+
name: string
|
|
289
|
+
ref: string
|
|
290
|
+
skipped?: boolean
|
|
291
|
+
}> = []
|
|
292
|
+
const pluginSkillKeys = new Set<string>()
|
|
293
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
294
|
+
cwd: params.workspaceFolder,
|
|
295
|
+
configs: [params.state.effectiveProjectConfig ?? params.state.projectConfig, params.state.userConfig],
|
|
296
|
+
useDefaultOneworksMcpServer: false
|
|
297
|
+
})
|
|
298
|
+
const pluginSkills = bundle.skills.filter(asset => asset.origin === 'plugin')
|
|
299
|
+
|
|
300
|
+
const runProgressStep = async <T>(label: string, callback: () => Promise<T>) => {
|
|
301
|
+
params.progress?.startStep(label)
|
|
302
|
+
try {
|
|
303
|
+
const result = await callback()
|
|
304
|
+
params.progress?.completeStep(label)
|
|
305
|
+
return result
|
|
306
|
+
} catch (error) {
|
|
307
|
+
params.progress?.failStep(label)
|
|
308
|
+
throw error
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const syncSkill = async (syncParams: SyncSkillParams): Promise<void> => {
|
|
313
|
+
const lockKey = syncParams.scope.kind === 'project'
|
|
314
|
+
? syncParams.normalized.targetDirName
|
|
315
|
+
: `${syncParams.scope.pluginInstance}/${syncParams.normalized.targetDirName}`
|
|
316
|
+
const existing = syncParams.scope.seen.get(lockKey)
|
|
317
|
+
const constraints = [
|
|
318
|
+
...(existing?.constraints ?? []),
|
|
319
|
+
...(syncParams.constraint == null ? [] : [syncParams.constraint])
|
|
320
|
+
]
|
|
321
|
+
const dependencyOf = toUniqueStrings([
|
|
322
|
+
...(existing?.dependencyOf ?? []),
|
|
323
|
+
...(syncParams.dependencyOf == null ? [] : [syncParams.dependencyOf])
|
|
324
|
+
])
|
|
325
|
+
|
|
326
|
+
if (existing != null) {
|
|
327
|
+
assertSameSource(existing.normalized, syncParams.normalized)
|
|
328
|
+
assertVersionConstraintsCompatible({
|
|
329
|
+
constraints,
|
|
330
|
+
name: syncParams.normalized.targetName
|
|
331
|
+
})
|
|
332
|
+
existing.constraints = constraints
|
|
333
|
+
existing.dependencyOf = dependencyOf
|
|
334
|
+
const entry = syncParams.scope.kind === 'project'
|
|
335
|
+
? nextLockfile.skills?.[lockKey]
|
|
336
|
+
: nextLockfile.pluginSkills?.[lockKey]
|
|
337
|
+
if (entry != null) {
|
|
338
|
+
entry.constraints = constraints.length === 0 ? undefined : constraints
|
|
339
|
+
entry.dependencyOf = dependencyOf.length === 0 ? undefined : dependencyOf
|
|
340
|
+
}
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
syncParams.scope.seen.set(lockKey, {
|
|
345
|
+
constraints,
|
|
346
|
+
dependencyOf,
|
|
347
|
+
normalized: syncParams.normalized
|
|
348
|
+
})
|
|
349
|
+
assertVersionConstraintsCompatible({
|
|
350
|
+
constraints,
|
|
351
|
+
name: syncParams.normalized.targetName
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
const previousEntry = syncParams.scope.kind === 'project'
|
|
355
|
+
? lockfile.skills?.[lockKey]
|
|
356
|
+
: lockfile.pluginSkills?.[lockKey]
|
|
357
|
+
const result = syncParams.installedResult ?? await runProgressStep(
|
|
358
|
+
formatSkillProgressLabel(syncParams),
|
|
359
|
+
() =>
|
|
360
|
+
installProjectSkill({
|
|
361
|
+
expectedHash: previousEntry?.hash,
|
|
362
|
+
force: params.force,
|
|
363
|
+
installPathSegments: syncParams.scope.installPathSegments,
|
|
364
|
+
registry: params.registry,
|
|
365
|
+
skill: syncParams.normalized,
|
|
366
|
+
workspaceFolder: params.workspaceFolder
|
|
367
|
+
})
|
|
368
|
+
)
|
|
369
|
+
const previousInstallDir = previousEntry == null
|
|
370
|
+
? undefined
|
|
371
|
+
: path.resolve(params.workspaceFolder, previousEntry.installPath)
|
|
372
|
+
const movedPreviousInstall = previousInstallDir != null &&
|
|
373
|
+
normalizeInstallPath(previousInstallDir) !== normalizeInstallPath(result.installDir)
|
|
374
|
+
if (movedPreviousInstall) {
|
|
375
|
+
await assertSkillDirectoryUnchanged({
|
|
376
|
+
expectedHash: previousEntry?.hash,
|
|
377
|
+
installDir: previousInstallDir
|
|
378
|
+
})
|
|
379
|
+
await rm(previousInstallDir, { recursive: true, force: true })
|
|
380
|
+
await pruneEmptyAncestorDirs({
|
|
381
|
+
startDir: previousInstallDir,
|
|
382
|
+
stopDir: resolveProjectOoPath(params.workspaceFolder, process.env, 'skills')
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
installed.push({
|
|
387
|
+
dirName: result.dirName,
|
|
388
|
+
hash: result.hash,
|
|
389
|
+
installDir: result.installDir,
|
|
390
|
+
name: result.name,
|
|
391
|
+
ref: result.ref,
|
|
392
|
+
skipped: params.force !== true && previousEntry != null && !movedPreviousInstall
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
const dependencies = await readDependenciesOrEmpty(result.skillPath)
|
|
396
|
+
const dependencyNames = dependencies
|
|
397
|
+
.map(dependency => normalizeDependencyInstall(dependency)?.targetDirName)
|
|
398
|
+
.filter((value): value is string => value != null)
|
|
399
|
+
|
|
400
|
+
const entry = buildProjectSkillLockEntry({
|
|
401
|
+
constraints,
|
|
402
|
+
dependencies: dependencyNames,
|
|
403
|
+
dependencyOf,
|
|
404
|
+
hash: result.hash,
|
|
405
|
+
installDir: result.installDir,
|
|
406
|
+
name: syncParams.normalized.targetName,
|
|
407
|
+
...(syncParams.scope.pluginInstance == null ? {} : { pluginInstance: syncParams.scope.pluginInstance }),
|
|
408
|
+
...(syncParams.scope.pluginInstancePath == null
|
|
409
|
+
? {}
|
|
410
|
+
: { pluginInstancePath: syncParams.scope.pluginInstancePath }),
|
|
411
|
+
registry: params.registry ?? syncParams.normalized.registry,
|
|
412
|
+
requested: syncParams.requested,
|
|
413
|
+
source: syncParams.normalized.source,
|
|
414
|
+
version: syncParams.normalized.version,
|
|
415
|
+
workspaceFolder: params.workspaceFolder
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
if (syncParams.scope.kind === 'project') {
|
|
419
|
+
nextLockfile.skills = {
|
|
420
|
+
...(nextLockfile.skills ?? {}),
|
|
421
|
+
[lockKey]: entry
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
nextLockfile.pluginSkills = {
|
|
425
|
+
...(nextLockfile.pluginSkills ?? {}),
|
|
426
|
+
[lockKey]: entry
|
|
427
|
+
}
|
|
428
|
+
pluginSkillKeys.add(lockKey)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
for (const dependency of dependencies) {
|
|
432
|
+
const pluginDependency = findPluginSkillDependency(pluginSkills, syncParams.scope, dependency)
|
|
433
|
+
if (pluginDependency != null) {
|
|
434
|
+
await syncPluginSkillRoot(syncParams.scope, pluginDependency)
|
|
435
|
+
continue
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const normalizedDependency = normalizeDependencyInstall(dependency)
|
|
439
|
+
if (normalizedDependency == null) continue
|
|
440
|
+
await syncSkill({
|
|
441
|
+
constraint: dependency.version == null
|
|
442
|
+
? undefined
|
|
443
|
+
: {
|
|
444
|
+
from: syncParams.scope.kind === 'project'
|
|
445
|
+
? syncParams.normalized.targetName
|
|
446
|
+
: `plugin:${syncParams.scope.pluginInstance}`,
|
|
447
|
+
version: dependency.version
|
|
448
|
+
},
|
|
449
|
+
dependencyOf: syncParams.scope.kind === 'project'
|
|
450
|
+
? syncParams.normalized.targetName
|
|
451
|
+
: `plugin:${syncParams.scope.pluginInstance}`,
|
|
452
|
+
normalized: normalizedDependency,
|
|
453
|
+
requested: false,
|
|
454
|
+
scope: syncParams.scope
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const syncPluginSkillRoot = async (scope: ScopeState, asset: SkillAsset) => {
|
|
460
|
+
if (scope.pluginRootSeen?.has(asset.id)) return
|
|
461
|
+
scope.pluginRootSeen?.add(asset.id)
|
|
462
|
+
|
|
463
|
+
const dependencies = await readDependenciesOrEmpty(asset.sourcePath)
|
|
464
|
+
for (const dependency of dependencies) {
|
|
465
|
+
const pluginDependency = findPluginSkillDependency(pluginSkills, scope, dependency)
|
|
466
|
+
if (pluginDependency != null) {
|
|
467
|
+
await syncPluginSkillRoot(scope, pluginDependency)
|
|
468
|
+
continue
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const normalizedDependency = normalizeDependencyInstall(dependency)
|
|
472
|
+
if (normalizedDependency == null) continue
|
|
473
|
+
await syncSkill({
|
|
474
|
+
constraint: dependency.version == null
|
|
475
|
+
? undefined
|
|
476
|
+
: { from: `plugin:${asset.displayName}`, version: dependency.version },
|
|
477
|
+
dependencyOf: `plugin:${asset.displayName}`,
|
|
478
|
+
normalized: normalizedDependency,
|
|
479
|
+
requested: false,
|
|
480
|
+
scope
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const projectSeen = new Map<string, SeenSkill>()
|
|
486
|
+
const createProjectScope = (installPathSegments: string[]): ScopeState => ({
|
|
487
|
+
installPathSegments,
|
|
488
|
+
kind: 'project',
|
|
489
|
+
seen: projectSeen
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
for (const rawTarget of params.targets) {
|
|
493
|
+
const syncTarget = normalizeSyncTarget(rawTarget)
|
|
494
|
+
const target = syncTarget.declaration
|
|
495
|
+
const baseInstallPathSegments = syncTarget.installPathSegments ?? []
|
|
496
|
+
if (isConfiguredSkillCollectionInstall(target)) {
|
|
497
|
+
const installPathSegments = resolveCollectionInstallPathSegments(target, baseInstallPathSegments)
|
|
498
|
+
const collectionResults = await runProgressStep(
|
|
499
|
+
formatCollectionProgressLabel(target),
|
|
500
|
+
() =>
|
|
501
|
+
installProjectSkillCollection({
|
|
502
|
+
expectedHashes: Object.fromEntries(
|
|
503
|
+
Object.entries(lockfile.skills ?? {}).map(([key, entry]) => [key, entry.hash])
|
|
504
|
+
),
|
|
505
|
+
force: params.force,
|
|
506
|
+
include: target.include,
|
|
507
|
+
installPathSegments,
|
|
508
|
+
registry: params.registry ?? target.registry,
|
|
509
|
+
source: target.source,
|
|
510
|
+
version: target.version,
|
|
511
|
+
workspaceFolder: params.workspaceFolder
|
|
512
|
+
})
|
|
513
|
+
)
|
|
514
|
+
for (const result of collectionResults) {
|
|
515
|
+
await syncSkill({
|
|
516
|
+
installedResult: result,
|
|
517
|
+
normalized: result.normalized,
|
|
518
|
+
requested: true,
|
|
519
|
+
scope: createProjectScope(installPathSegments)
|
|
520
|
+
})
|
|
521
|
+
}
|
|
522
|
+
continue
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const normalized = normalizeProjectSkillInstall(target)
|
|
526
|
+
if (normalized == null) continue
|
|
527
|
+
await syncSkill({
|
|
528
|
+
normalized,
|
|
529
|
+
requested: true,
|
|
530
|
+
scope: createProjectScope(baseInstallPathSegments)
|
|
531
|
+
})
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const pluginScopes = new Map<string, ScopeState>()
|
|
535
|
+
for (const asset of pluginSkills) {
|
|
536
|
+
const pluginInstance = toPluginInstanceKey(asset)
|
|
537
|
+
const scope = pluginScopes.get(pluginInstance) ?? {
|
|
538
|
+
installPathSegments: ['.plugins', pluginInstance],
|
|
539
|
+
kind: 'plugin',
|
|
540
|
+
pluginInstance,
|
|
541
|
+
pluginInstancePath: asset.instancePath,
|
|
542
|
+
pluginRootSeen: new Set<string>(),
|
|
543
|
+
seen: new Map()
|
|
544
|
+
} satisfies ScopeState
|
|
545
|
+
pluginScopes.set(pluginInstance, scope)
|
|
546
|
+
await syncPluginSkillRoot(scope, asset)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
for (const [key, entry] of Object.entries(lockfile.pluginSkills ?? {})) {
|
|
550
|
+
if (pluginSkillKeys.has(key)) continue
|
|
551
|
+
if (!entry.installPath.startsWith('.oo/skills/.plugins/')) continue
|
|
552
|
+
const installDir = path.resolve(params.workspaceFolder, entry.installPath)
|
|
553
|
+
await assertSkillDirectoryUnchanged({
|
|
554
|
+
expectedHash: entry.hash,
|
|
555
|
+
installDir
|
|
556
|
+
})
|
|
557
|
+
await rm(installDir, { recursive: true, force: true })
|
|
558
|
+
delete nextLockfile.pluginSkills?.[key]
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
await Promise.all([
|
|
562
|
+
pruneEmptyDirectoryTree(resolveProjectOoPath(params.workspaceFolder, process.env, 'skills', '.extends')),
|
|
563
|
+
pruneEmptyDirectoryTree(resolveProjectOoPath(params.workspaceFolder, process.env, 'skills', '.plugins'))
|
|
564
|
+
])
|
|
565
|
+
|
|
566
|
+
await writeProjectSkillsLockfile(params.workspaceFolder, nextLockfile)
|
|
567
|
+
return {
|
|
568
|
+
installed,
|
|
569
|
+
lockfile: nextLockfile
|
|
570
|
+
}
|
|
571
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const CONFIG_WRITE_SOURCES = ['global', 'project', 'user'] as const
|
|
2
|
+
export const CONFIG_REMOVE_SOURCES = ['global', 'project', 'user', 'all'] as const
|
|
3
|
+
|
|
4
|
+
export type ConfigWriteSource = typeof CONFIG_WRITE_SOURCES[number]
|
|
5
|
+
export type ConfigRemoveSource = typeof CONFIG_REMOVE_SOURCES[number]
|
|
6
|
+
|
|
7
|
+
export interface SkillsInstallOptions {
|
|
8
|
+
force?: boolean
|
|
9
|
+
json?: boolean
|
|
10
|
+
registry?: string
|
|
11
|
+
rename?: string
|
|
12
|
+
source?: string
|
|
13
|
+
version?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SkillsAddOptions extends SkillsInstallOptions {
|
|
17
|
+
configSource?: ConfigWriteSource
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SkillsRemoveOptions {
|
|
21
|
+
configSource?: ConfigRemoveSource
|
|
22
|
+
json?: boolean
|
|
23
|
+
keepFiles?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SkillsPublishOptions {
|
|
27
|
+
access?: string
|
|
28
|
+
group?: boolean | string
|
|
29
|
+
json?: boolean
|
|
30
|
+
region?: string
|
|
31
|
+
registry?: string
|
|
32
|
+
yes?: boolean
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerSkillsCommand } from './skills/register'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
|
|
3
|
+
import type { Command } from 'commander'
|
|
4
|
+
|
|
5
|
+
import { formatListCommand, formatResumeCommand } from '#~/session-cache.js'
|
|
6
|
+
import { resolveCliWorkspaceCwd } from '#~/workspace.js'
|
|
7
|
+
|
|
8
|
+
import { signalCliSession } from './session-control'
|
|
9
|
+
|
|
10
|
+
export function registerStopCommand(program: Command) {
|
|
11
|
+
program
|
|
12
|
+
.command('stop <sessionId>')
|
|
13
|
+
.description('Stop a running CLI session')
|
|
14
|
+
.addHelpText(
|
|
15
|
+
'after',
|
|
16
|
+
`
|
|
17
|
+
Examples:
|
|
18
|
+
oneworks list --running
|
|
19
|
+
oneworks stop <sessionId>
|
|
20
|
+
`
|
|
21
|
+
)
|
|
22
|
+
.action(async (sessionId: string) => {
|
|
23
|
+
try {
|
|
24
|
+
const result = await signalCliSession({
|
|
25
|
+
cwd: resolveCliWorkspaceCwd(),
|
|
26
|
+
sessionId,
|
|
27
|
+
signal: 'SIGTERM'
|
|
28
|
+
})
|
|
29
|
+
console.log(result.message)
|
|
30
|
+
console.log(
|
|
31
|
+
`Tips:\n Check running sessions: ${formatListCommand({ running: true })}\n Resume later: ${
|
|
32
|
+
formatResumeCommand(sessionId)
|
|
33
|
+
}`
|
|
34
|
+
)
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
37
|
+
console.error(message)
|
|
38
|
+
process.exit(1)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defineConfig } from '@oneworks/config'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import type { PluginConfig } from '@oneworks/types'
|
|
5
|
+
|
|
6
|
+
const CLI_DEFAULT_SKILL_PLUGIN_ID = '@oneworks/plugin-cli-skills'
|
|
7
|
+
const requireFromCliPackage = createRequire(__filename)
|
|
8
|
+
|
|
9
|
+
const CLI_DEFAULT_SKILL_NAMES = [
|
|
10
|
+
'oneworks-cli-quickstart',
|
|
11
|
+
'oneworks-cli-print-mode',
|
|
12
|
+
'oneworks-channel',
|
|
13
|
+
'oneworks-mem',
|
|
14
|
+
'create-entity',
|
|
15
|
+
'update-entity',
|
|
16
|
+
'create-plugin'
|
|
17
|
+
] as const
|
|
18
|
+
|
|
19
|
+
const resolveCliDefaultSkillPluginRoot = () => (
|
|
20
|
+
dirname(requireFromCliPackage.resolve(`${CLI_DEFAULT_SKILL_PLUGIN_ID}/package.json`))
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
export const getCliDefaultSkillPluginConfig = (): PluginConfig => [
|
|
24
|
+
{
|
|
25
|
+
id: resolveCliDefaultSkillPluginRoot()
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
export const getCliDefaultSkillNames = () => [...CLI_DEFAULT_SKILL_NAMES]
|
package/src/env.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@oneworks/register/dotenv'
|