@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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/channel.js +7 -0
  3. package/cli.js +5 -0
  4. package/mem.js +7 -0
  5. package/package.json +59 -0
  6. package/postinstall.js +75 -0
  7. package/src/AGENTS.md +169 -0
  8. package/src/channel-cli.ts +19 -0
  9. package/src/cli-argv.ts +27 -0
  10. package/src/cli.ts +63 -0
  11. package/src/commands/@core/adapter-option.ts +85 -0
  12. package/src/commands/@core/extra-options.ts +12 -0
  13. package/src/commands/@core/plugin-install.ts +1 -0
  14. package/src/commands/@core/plugin-source.ts +1 -0
  15. package/src/commands/accounts.ts +204 -0
  16. package/src/commands/adapter/prepare-selection.ts +181 -0
  17. package/src/commands/adapter/prepare.ts +104 -0
  18. package/src/commands/adapter.ts +48 -0
  19. package/src/commands/agent/actions.ts +176 -0
  20. package/src/commands/agent/runtime-store-commands.ts +56 -0
  21. package/src/commands/agent/runtime-store-events.ts +23 -0
  22. package/src/commands/agent/runtime-store-session.ts +170 -0
  23. package/src/commands/agent/runtime-store-shared.ts +139 -0
  24. package/src/commands/agent/runtime-store.ts +4 -0
  25. package/src/commands/agent.ts +81 -0
  26. package/src/commands/benchmark.ts +198 -0
  27. package/src/commands/channel.ts +594 -0
  28. package/src/commands/clear.ts +140 -0
  29. package/src/commands/config/actions.ts +196 -0
  30. package/src/commands/config/display-state.ts +108 -0
  31. package/src/commands/config/index.ts +135 -0
  32. package/src/commands/config/interactive.ts +121 -0
  33. package/src/commands/config/read-state.ts +56 -0
  34. package/src/commands/config/section-state.ts +109 -0
  35. package/src/commands/config/shared.ts +195 -0
  36. package/src/commands/kill.ts +41 -0
  37. package/src/commands/list.ts +224 -0
  38. package/src/commands/memory/context.ts +76 -0
  39. package/src/commands/memory/entries.ts +131 -0
  40. package/src/commands/memory/shared.ts +89 -0
  41. package/src/commands/memory/store.ts +69 -0
  42. package/src/commands/memory/target.ts +54 -0
  43. package/src/commands/memory.ts +97 -0
  44. package/src/commands/plugin.ts +62 -0
  45. package/src/commands/report-targets.ts +149 -0
  46. package/src/commands/report.ts +232 -0
  47. package/src/commands/run/adapter-cli-version.ts +65 -0
  48. package/src/commands/run/command.ts +982 -0
  49. package/src/commands/run/input-bridge.ts +108 -0
  50. package/src/commands/run/input-control.ts +112 -0
  51. package/src/commands/run/input-decision.ts +88 -0
  52. package/src/commands/run/options.ts +104 -0
  53. package/src/commands/run/output.ts +179 -0
  54. package/src/commands/run/permission-decision.ts +19 -0
  55. package/src/commands/run/permission-recovery.ts +194 -0
  56. package/src/commands/run/permission-state.ts +177 -0
  57. package/src/commands/run/print-idle-timeout.ts +47 -0
  58. package/src/commands/run/protocol-envelope.ts +111 -0
  59. package/src/commands/run/protocol-stdio.ts +71 -0
  60. package/src/commands/run/protocol.ts +391 -0
  61. package/src/commands/run/runtime-command-bridge.ts +190 -0
  62. package/src/commands/run/runtime-event-sink.ts +560 -0
  63. package/src/commands/run/session-exit-controller.ts +45 -0
  64. package/src/commands/run/types.ts +65 -0
  65. package/src/commands/run.ts +62 -0
  66. package/src/commands/session-control.ts +133 -0
  67. package/src/commands/skills/add-command.ts +88 -0
  68. package/src/commands/skills/install-command.ts +105 -0
  69. package/src/commands/skills/install.ts +216 -0
  70. package/src/commands/skills/progress.ts +126 -0
  71. package/src/commands/skills/publish-command.ts +85 -0
  72. package/src/commands/skills/register.ts +17 -0
  73. package/src/commands/skills/remove-command.ts +102 -0
  74. package/src/commands/skills/shared.ts +117 -0
  75. package/src/commands/skills/sync.ts +571 -0
  76. package/src/commands/skills/types.ts +33 -0
  77. package/src/commands/skills.ts +1 -0
  78. package/src/commands/stop.ts +41 -0
  79. package/src/config.ts +1 -0
  80. package/src/default-skill-plugin.ts +29 -0
  81. package/src/env.ts +1 -0
  82. package/src/hooks/plugins/index.ts +66 -0
  83. package/src/mem-cli.ts +19 -0
  84. package/src/session-cache.ts +250 -0
  85. package/src/session-permission-cache.ts +40 -0
  86. package/src/utils.ts +25 -0
  87. 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'