@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,126 @@
1
+ import process from 'node:process'
2
+
3
+ export interface SkillsProgressReporter {
4
+ completeStep: (label: string) => void
5
+ fail: (label?: string) => void
6
+ failStep: (label: string) => void
7
+ finish: (summary?: string) => void
8
+ startStep: (label: string) => void
9
+ }
10
+
11
+ const BAR_WIDTH = 20
12
+ const TICK_MS = 120
13
+ const SPINNER_FRAMES = ['-', '\\', '|', '/']
14
+
15
+ const formatElapsed = (startedAt: number) => {
16
+ const totalSeconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1000))
17
+ const minutes = Math.floor(totalSeconds / 60)
18
+ const seconds = totalSeconds % 60
19
+ return `${minutes}:${seconds.toString().padStart(2, '0')}`
20
+ }
21
+
22
+ const trimToWidth = (value: string, width: number) => (
23
+ value.length <= width ? value : `${value.slice(0, Math.max(0, width - 3))}...`
24
+ )
25
+
26
+ export const createSkillsProgress = (params: {
27
+ enabled?: boolean
28
+ } = {}): SkillsProgressReporter => {
29
+ const enabled = params.enabled !== false && process.env.VITEST_WORKER_ID == null
30
+ const startedAt = Date.now()
31
+ const tty = Boolean(process.stderr.isTTY)
32
+ let activeLabel: string | undefined
33
+ let completed = 0
34
+ let failed = false
35
+ let frame = 0
36
+ let interval: NodeJS.Timeout | undefined
37
+ let total = 0
38
+ let wrote = false
39
+
40
+ const buildLine = (label = activeLabel ?? 'Done') => {
41
+ const ratio = total === 0 ? 1 : completed / total
42
+ const filled = Math.max(0, Math.min(BAR_WIDTH, Math.round(ratio * BAR_WIDTH)))
43
+ const bar = `${'#'.repeat(filled)}${'.'.repeat(BAR_WIDTH - filled)}`
44
+ const spinner = activeLabel == null ? ' ' : SPINNER_FRAMES[frame % SPINNER_FRAMES.length]
45
+ return `[oneworks] ${spinner} [${bar}] ${completed}/${total} ${trimToWidth(label, 72)} ${formatElapsed(startedAt)}`
46
+ }
47
+
48
+ const render = () => {
49
+ if (!enabled || total === 0) return
50
+ wrote = true
51
+ if (tty) {
52
+ process.stderr.write(`\r${buildLine()}\x1B[K`)
53
+ frame++
54
+ return
55
+ }
56
+
57
+ if (activeLabel != null) {
58
+ process.stderr.write(`${buildLine(activeLabel)}\n`)
59
+ }
60
+ }
61
+
62
+ const ensureTicker = () => {
63
+ if (!enabled || !tty || interval != null) return
64
+ interval = setInterval(render, TICK_MS)
65
+ interval.unref()
66
+ }
67
+
68
+ const stopTicker = () => {
69
+ if (interval == null) return
70
+ clearInterval(interval)
71
+ interval = undefined
72
+ }
73
+
74
+ const finishLine = (summary?: string) => {
75
+ if (!enabled || !wrote) return
76
+ const label = summary ?? (failed ? 'Failed' : 'Done')
77
+ if (tty) {
78
+ process.stderr.write(`\r${buildLine(label)}\x1B[K\n`)
79
+ return
80
+ }
81
+ process.stderr.write(`[oneworks] ${label} (${formatElapsed(startedAt)})\n`)
82
+ }
83
+
84
+ return {
85
+ startStep(label) {
86
+ if (!enabled) return
87
+ total++
88
+ activeLabel = label
89
+ render()
90
+ ensureTicker()
91
+ },
92
+ completeStep(label) {
93
+ if (!enabled) return
94
+ completed = Math.min(total, completed + 1)
95
+ activeLabel = undefined
96
+ if (!tty) {
97
+ process.stderr.write(`[oneworks] done ${label} (${completed}/${total}, ${formatElapsed(startedAt)})\n`)
98
+ } else {
99
+ render()
100
+ }
101
+ },
102
+ failStep(label) {
103
+ if (!enabled) return
104
+ failed = true
105
+ activeLabel = undefined
106
+ if (!tty) {
107
+ process.stderr.write(`[oneworks] failed ${label} (${formatElapsed(startedAt)})\n`)
108
+ } else {
109
+ render()
110
+ }
111
+ },
112
+ fail(label) {
113
+ if (!enabled) return
114
+ failed = true
115
+ activeLabel = undefined
116
+ stopTicker()
117
+ finishLine(label ?? 'Failed')
118
+ },
119
+ finish(summary) {
120
+ if (!enabled) return
121
+ activeLabel = undefined
122
+ stopTicker()
123
+ finishLine(summary)
124
+ }
125
+ }
126
+ }
@@ -0,0 +1,85 @@
1
+ import type { Command } from 'commander'
2
+
3
+ import {
4
+ publishSkillsCli,
5
+ resolveConfiguredSkillRegistries,
6
+ resolveProjectSkillPublishSpec,
7
+ resolveSkillsRegistry
8
+ } from '@oneworks/utils'
9
+
10
+ import { resolveCliWorkspaceCwd } from '#~/workspace.js'
11
+
12
+ import { exitWithError, loadSkillsConfigState, printResult } from './shared'
13
+ import type { SkillsPublishOptions } from './types'
14
+
15
+ export const registerPublishSkillSubcommand = (skillsCommand: Command) => {
16
+ skillsCommand
17
+ .command('publish <skill>')
18
+ .description('Publish a local project skill, local path, or remote skill spec through the active skills CLI')
19
+ .option('--access <access>', 'Publish access level passed to the skills CLI')
20
+ .option('--group [name]', 'Publish to a specific group; pass bare --group to select interactively')
21
+ .option('--region <region>', 'Publish region passed to the skills CLI')
22
+ .option('--registry <registry>', 'Package registry used to install the managed skills CLI')
23
+ .option('-y, --yes', 'Skip confirmation prompts when the underlying skills CLI supports it', false)
24
+ .option('--json', 'Print JSON output', false)
25
+ .action(async (skill: string, opts: SkillsPublishOptions) => {
26
+ try {
27
+ const workspaceFolder = resolveCliWorkspaceCwd()
28
+ const state = await loadSkillsConfigState(workspaceFolder)
29
+ const resolved = await resolveProjectSkillPublishSpec({
30
+ selector: skill,
31
+ workspaceFolder
32
+ })
33
+ const publishSource = typeof resolved.publish?.source === 'string'
34
+ ? resolved.publish.source
35
+ : resolved.publish?.registry
36
+ const boundRegistry = typeof publishSource === 'string'
37
+ ? resolveConfiguredSkillRegistries(state.mergedConfig).find(entry => entry.source === publishSource)
38
+ : undefined
39
+ if (publishSource != null && boundRegistry == null && opts.registry == null) {
40
+ throw new Error(
41
+ `Configured publish source "${publishSource}" was not found in skillRegistries.`
42
+ )
43
+ }
44
+ const published = await publishSkillsCli({
45
+ access: opts.access ?? resolved.publish?.access ?? boundRegistry?.publish?.access,
46
+ cwd: workspaceFolder,
47
+ group: opts.group ?? resolved.publish?.group ?? boundRegistry?.publish?.group,
48
+ region: opts.region ?? resolved.publish?.region ?? boundRegistry?.publish?.region,
49
+ registry: opts.registry ?? boundRegistry?.registry ?? resolveSkillsRegistry(state.mergedConfig.skills),
50
+ skillSpec: resolved.skillSpec,
51
+ yes: opts.yes
52
+ })
53
+
54
+ if (opts.json) {
55
+ printResult({
56
+ action: 'publish',
57
+ boundSource: boundRegistry?.source,
58
+ output: published.output,
59
+ requested: resolved.requested,
60
+ skillSpec: resolved.skillSpec,
61
+ source: resolved.kind,
62
+ workspaceFolder
63
+ }, true)
64
+ return
65
+ }
66
+
67
+ const output = published.output.trim()
68
+ if (output !== '') {
69
+ console.log(output)
70
+ return
71
+ }
72
+
73
+ printResult({
74
+ action: 'publish',
75
+ ...(boundRegistry != null ? { boundSource: boundRegistry.source } : {}),
76
+ requested: resolved.requested,
77
+ skillSpec: resolved.skillSpec,
78
+ source: resolved.kind,
79
+ workspaceFolder
80
+ })
81
+ } catch (error) {
82
+ exitWithError(error, opts.json)
83
+ }
84
+ })
85
+ }
@@ -0,0 +1,17 @@
1
+ import type { Command } from 'commander'
2
+
3
+ import { registerAddSkillSubcommand } from './add-command'
4
+ import { registerInstallSkillSubcommands } from './install-command'
5
+ import { registerPublishSkillSubcommand } from './publish-command'
6
+ import { registerRemoveSkillSubcommand } from './remove-command'
7
+
8
+ export function registerSkillsCommand(program: Command) {
9
+ const skillsCommand = program
10
+ .command('skills')
11
+ .description('Install and manage project skills declared in workspace config')
12
+
13
+ registerAddSkillSubcommand(skillsCommand)
14
+ registerInstallSkillSubcommands(skillsCommand)
15
+ registerRemoveSkillSubcommand(skillsCommand)
16
+ registerPublishSkillSubcommand(skillsCommand)
17
+ }
@@ -0,0 +1,102 @@
1
+ import { Option } from 'commander'
2
+ import type { Command } from 'commander'
3
+
4
+ import type { ConfigSource } from '@oneworks/config'
5
+ import { updateConfigFile } from '@oneworks/config'
6
+ import { normalizeProjectSkillInstall, removeProjectSkill, resolveConfiguredSkillInstalls } from '@oneworks/utils'
7
+
8
+ import { resolveCliWorkspaceCwd } from '#~/workspace.js'
9
+
10
+ import {
11
+ buildGeneralSkillsUpdateValue,
12
+ exitWithError,
13
+ getRawSourceConfig,
14
+ loadSkillsConfigState,
15
+ matchesSkillSelector,
16
+ normalizeString,
17
+ printResult,
18
+ resolveInstalledSkillDirNames
19
+ } from './shared'
20
+ import { CONFIG_REMOVE_SOURCES } from './types'
21
+ import type { SkillsRemoveOptions } from './types'
22
+
23
+ export const registerRemoveSkillSubcommand = (skillsCommand: Command) => {
24
+ skillsCommand
25
+ .command('remove <skill>')
26
+ .description('Remove a configured project skill and delete the local installed directory')
27
+ .addOption(
28
+ new Option('--config-source <source>', 'Config source(s) to update').choices([...CONFIG_REMOVE_SOURCES]).default(
29
+ 'all'
30
+ )
31
+ )
32
+ .option('--keep-files', 'Only update config and keep local installed files', false)
33
+ .option('--json', 'Print JSON output', false)
34
+ .action(async (skill: string, opts: SkillsRemoveOptions) => {
35
+ try {
36
+ const workspaceFolder = resolveCliWorkspaceCwd()
37
+ const state = await loadSkillsConfigState(workspaceFolder)
38
+ const selector = normalizeString(skill)
39
+ if (selector == null) {
40
+ throw new Error('Skill selector is required.')
41
+ }
42
+
43
+ const removedConfigSources: string[] = []
44
+ const removedDirNames = new Set<string>()
45
+ const sources: ConfigSource[] = opts.configSource === 'all'
46
+ ? ['global', 'project', 'user']
47
+ : [opts.configSource ?? 'project']
48
+
49
+ for (const source of sources) {
50
+ const sourceConfig = getRawSourceConfig(state, source)
51
+ const configured = resolveConfiguredSkillInstalls(sourceConfig?.skills)
52
+ const remaining = configured.filter(item => !matchesSkillSelector(selector, item))
53
+ const matched = configured.filter(item => matchesSkillSelector(selector, item))
54
+ if (matched.length === 0) continue
55
+
56
+ for (const item of matched) {
57
+ const normalized = normalizeProjectSkillInstall(item)
58
+ if (normalized != null) {
59
+ removedDirNames.add(normalized.targetDirName)
60
+ }
61
+ }
62
+
63
+ await updateConfigFile({
64
+ workspaceFolder,
65
+ source,
66
+ section: 'general',
67
+ value: buildGeneralSkillsUpdateValue(sourceConfig, remaining)
68
+ })
69
+ removedConfigSources.push(source)
70
+ }
71
+
72
+ const installedDirNames = await resolveInstalledSkillDirNames(workspaceFolder, selector)
73
+ for (const dirName of installedDirNames) {
74
+ removedDirNames.add(dirName)
75
+ }
76
+
77
+ const removedDirs = opts.keepFiles === true
78
+ ? []
79
+ : await Promise.all(
80
+ Array.from(removedDirNames).map(dirName =>
81
+ removeProjectSkill({
82
+ dirName,
83
+ workspaceFolder
84
+ })
85
+ )
86
+ )
87
+
88
+ if (removedConfigSources.length === 0 && removedDirs.length === 0) {
89
+ throw new Error(`No configured or installed skill matched "${selector}".`)
90
+ }
91
+
92
+ printResult({
93
+ action: 'remove',
94
+ removedConfigSources,
95
+ removedDirs: removedDirs.map(item => item.installDir),
96
+ workspaceFolder
97
+ }, opts.json)
98
+ } catch (error) {
99
+ exitWithError(error, opts.json)
100
+ }
101
+ })
102
+ }
@@ -0,0 +1,117 @@
1
+ import { access } from 'node:fs/promises'
2
+ import process from 'node:process'
3
+
4
+ import type { ConfigSource } from '@oneworks/config'
5
+ import { buildConfigJsonVariables, loadConfigState } from '@oneworks/config'
6
+ import type { Config, ConfiguredSkillInstallConfig } from '@oneworks/types'
7
+ import {
8
+ buildSkillsConfigValue,
9
+ isSameDeclaredSkill,
10
+ matchesDeclaredSkillSelector,
11
+ readProjectSkills,
12
+ toSkillSlug
13
+ } from '@oneworks/utils'
14
+
15
+ const normalizeString = (value: unknown) => (
16
+ typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined
17
+ )
18
+
19
+ const pathExists = async (targetPath: string) => {
20
+ try {
21
+ await access(targetPath)
22
+ return true
23
+ } catch {
24
+ return false
25
+ }
26
+ }
27
+
28
+ export const printResult = (value: unknown, json = false) => {
29
+ if (json) {
30
+ console.log(JSON.stringify(
31
+ value != null && typeof value === 'object' && !Array.isArray(value)
32
+ ? { ok: true, ...(value as Record<string, unknown>) }
33
+ : { ok: true, value },
34
+ null,
35
+ 2
36
+ ))
37
+ return
38
+ }
39
+
40
+ if (typeof value === 'string') {
41
+ console.log(value)
42
+ return
43
+ }
44
+
45
+ console.log(JSON.stringify(value, null, 2))
46
+ }
47
+
48
+ export const exitWithError = (error: unknown, json = false): never => {
49
+ const message = error instanceof Error ? error.message : String(error)
50
+ if (json) {
51
+ console.error(JSON.stringify({ ok: false, error: message }, null, 2))
52
+ } else {
53
+ console.error(message)
54
+ }
55
+ process.exit(1)
56
+ }
57
+
58
+ export const loadSkillsConfigState = async (cwd: string) => (
59
+ await loadConfigState({
60
+ cwd,
61
+ jsonVariables: buildConfigJsonVariables(cwd, process.env)
62
+ })
63
+ )
64
+
65
+ export const getResolvedSourceConfig = (
66
+ state: Awaited<ReturnType<typeof loadSkillsConfigState>>,
67
+ source: ConfigSource
68
+ ) => {
69
+ switch (source) {
70
+ case 'global':
71
+ return state.globalConfig
72
+ case 'project':
73
+ return state.projectSource?.resolvedConfig
74
+ case 'user':
75
+ return state.userConfig
76
+ }
77
+ }
78
+
79
+ export const getRawSourceConfig = (state: Awaited<ReturnType<typeof loadSkillsConfigState>>, source: ConfigSource) => {
80
+ switch (source) {
81
+ case 'global':
82
+ return state.globalSource?.rawConfig
83
+ case 'project':
84
+ return state.projectSource?.rawConfig
85
+ case 'user':
86
+ return state.userSource?.rawConfig
87
+ }
88
+ }
89
+
90
+ export const buildGeneralSkillsUpdateValue = (
91
+ sourceConfig: Config | undefined,
92
+ nextSkills: Array<string | ConfiguredSkillInstallConfig>
93
+ ) => {
94
+ const value: Record<string, unknown> = {
95
+ skills: buildSkillsConfigValue({
96
+ items: nextSkills,
97
+ current: sourceConfig?.skills
98
+ })
99
+ }
100
+ return value
101
+ }
102
+
103
+ export const resolveInstalledSkillDirNames = async (workspaceFolder: string, selector: string) => {
104
+ const trimmedSelector = selector.trim()
105
+ const selectorSlug = toSkillSlug(trimmedSelector)
106
+ const skills = await readProjectSkills(workspaceFolder)
107
+ return skills
108
+ .filter(skill => (
109
+ skill.dirName === trimmedSelector ||
110
+ skill.name === trimmedSelector ||
111
+ skill.dirName === selectorSlug ||
112
+ toSkillSlug(skill.name) === selectorSlug
113
+ ))
114
+ .map(skill => skill.dirName)
115
+ }
116
+
117
+ export { isSameDeclaredSkill, matchesDeclaredSkillSelector as matchesSkillSelector, normalizeString, pathExists }