@newlogic-digital/cli 1.5.0-next.3 → 1.5.0-next.5
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/index.mjs +14 -2
- package/package.json +1 -1
- package/skills/{newlogic → newlogic-cli}/SKILL.md +1 -1
- package/skills/newlogic-cli/agents/openai.yaml +6 -0
- package/skills/newlogic-cli/assets/favicon.svg +3 -0
- package/skills/newlogic-cli/assets/maskable.png +0 -0
- package/src/commands/blocks/index.mjs +2 -1
- package/src/commands/blocks/service.mjs +79 -25
- package/src/commands/skills/index.mjs +62 -0
- package/src/utils.mjs +3 -2
- package/skills/newlogic/agents/openai.yaml +0 -4
package/index.mjs
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
import init from './src/commands/init/index.mjs'
|
|
4
4
|
import cms from './src/commands/cms/index.mjs'
|
|
5
5
|
import blocks from './src/commands/blocks/index.mjs'
|
|
6
|
+
import skills from './src/commands/skills/index.mjs'
|
|
6
7
|
import { styleText } from 'node:util'
|
|
7
8
|
import { version, name } from './src/utils.mjs'
|
|
8
9
|
|
|
9
|
-
const knownCommands = ['init', 'cms', 'blocks']
|
|
10
|
+
const knownCommands = ['init', 'cms', 'blocks', 'skills']
|
|
10
11
|
|
|
11
12
|
function normalizeOptionName(name) {
|
|
12
13
|
return name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
|
|
@@ -149,15 +150,19 @@ if (!command) {
|
|
|
149
150
|
|
|
150
151
|
-- blocks --
|
|
151
152
|
${styleText('green', 'newlogic blocks list')} - Lists all available installable blocks with descriptions
|
|
152
|
-
${styleText('green', 'newlogic blocks add')} ${styleText('yellow', '<name...>')} - Installs one or more blocks by kebab-case or PascalCase name
|
|
153
|
+
${styleText('green', 'newlogic blocks add')} ${styleText('yellow', '<name[@variant]...>')} - Installs one or more blocks by kebab-case or PascalCase name
|
|
153
154
|
${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', '<name...>')} - Removes one or more blocks and orphaned dependencies
|
|
154
155
|
${styleText('green', 'newlogic blocks update')} - Reinstalls all configured blocks from ${styleText('yellow', 'newlogic.config.json')}
|
|
155
156
|
${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'about-accordion')}
|
|
156
157
|
${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'AboutAccordion')}
|
|
158
|
+
${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'header-nav-left@stimulus')}
|
|
157
159
|
${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'header-nav-left contact-info-card hero-floating-text')}
|
|
158
160
|
${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', 'about-accordion')}
|
|
159
161
|
${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', 'header-nav-left hero-floating-text')}
|
|
160
162
|
${styleText('green', 'newlogic blocks update')}
|
|
163
|
+
|
|
164
|
+
-- skills --
|
|
165
|
+
${styleText('green', 'newlogic skills install')} - Installs the bundled ${styleText('yellow', 'newlogic-cli')} skill via ${styleText('blue', 'npx skills add')}
|
|
161
166
|
`)
|
|
162
167
|
|
|
163
168
|
process.exit(0)
|
|
@@ -187,6 +192,13 @@ if (command === 'blocks') {
|
|
|
187
192
|
await blocks(action, names)
|
|
188
193
|
}
|
|
189
194
|
|
|
195
|
+
if (command === 'skills') {
|
|
196
|
+
const { positionals } = parseCommandArgs(rawArgs.slice(1))
|
|
197
|
+
const action = positionals[0]
|
|
198
|
+
|
|
199
|
+
await skills(action)
|
|
200
|
+
}
|
|
201
|
+
|
|
190
202
|
if (command && !knownCommands.includes(command)) {
|
|
191
203
|
printUnknownCommand(command)
|
|
192
204
|
process.exit(1)
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: newlogic
|
|
2
|
+
name: newlogic-cli
|
|
3
3
|
description: Use the installed Newlogic CLI when Codex needs to run `newlogic`, scaffold `@newlogic-digital/ui` or `@newlogic-digital/cms` projects, prepare CMS templates and components, or manage installable blocks through `newlogic.config.json`. Trigger this skill for tasks involving the commands `init`, `cms`, or `blocks`, especially when an agent should run the CLI safely and non-interactively.
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "Newlogic CLI"
|
|
3
|
+
short_description: "Use installed Newlogic CLI for init, cms, and blocks."
|
|
4
|
+
icon_small: "./assets/favicon.svg"
|
|
5
|
+
icon_large: "./assets/maskable.png"
|
|
6
|
+
default_prompt: "Use $newlogic-cli to run Newlogic init, cms, or blocks commands safely and non-interactively."
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120">
|
|
2
|
+
<path fill="#000" d="M89,0,62.68,26.36,36.32,0H0V120H31L57.32,93.64,83.68,120H120V0Zm0,8.53V52.72l-22.1-22.1Zm6,58.75V6H114V109.7L10.3,6H33.82ZM31,111.47V67.28l22.1,22.1Zm-6-58.75V114H6V10.29L109.7,114H86.18Z"/>
|
|
3
|
+
</svg>
|
|
Binary file
|
|
@@ -134,13 +134,14 @@ function printBlocksUsage() {
|
|
|
134
134
|
styleText(['white', 'bold'], 'Usage:'),
|
|
135
135
|
'',
|
|
136
136
|
` ${styleText('green', 'newlogic blocks list')} - Lists all available blocks with descriptions`,
|
|
137
|
-
` ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', '<name...>')} - Adds one or more blocks by kebab-case or PascalCase name`,
|
|
137
|
+
` ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', '<name[@variant]...>')} - Adds one or more blocks by kebab-case or PascalCase name`,
|
|
138
138
|
` ${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', '<name...>')} - Removes one or more blocks and orphaned dependencies`,
|
|
139
139
|
` ${styleText('green', 'newlogic blocks update')} - Reinstalls all configured blocks from ${styleText('yellow', 'newlogic.config.json')}`,
|
|
140
140
|
'',
|
|
141
141
|
styleText(['white', 'bold'], 'Examples:'),
|
|
142
142
|
'',
|
|
143
143
|
` ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'header-nav-left contact-info-card hero-floating-text')}`,
|
|
144
|
+
` ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'header-nav-left@stimulus')}`,
|
|
144
145
|
` ${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', 'header-nav-left hero-floating-text')}`,
|
|
145
146
|
].join('\n'))
|
|
146
147
|
}
|
|
@@ -83,6 +83,51 @@ function assertBlockName(name, label = 'block name') {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
function parseBlockSpecifier(rawValue, {
|
|
87
|
+
label = 'block name',
|
|
88
|
+
allowObject = false,
|
|
89
|
+
} = {}) {
|
|
90
|
+
if (allowObject && rawValue && typeof rawValue === 'object' && !Array.isArray(rawValue)) {
|
|
91
|
+
const name = normalizeBlockName(rawValue.name)
|
|
92
|
+
const variant = rawValue.variant == null ? '' : `${rawValue.variant}`.trim()
|
|
93
|
+
|
|
94
|
+
assertBlockName(name, 'configured block name')
|
|
95
|
+
|
|
96
|
+
if (rawValue.variant != null && !variant) {
|
|
97
|
+
throw new Error(`Configured block "${name}" has an empty variant`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
name,
|
|
102
|
+
variant: variant || undefined,
|
|
103
|
+
variantExplicit: rawValue.variant != null,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const value = `${rawValue ?? ''}`.trim()
|
|
108
|
+
|
|
109
|
+
if (!value) {
|
|
110
|
+
throw new Error(`Missing ${label}`)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const variantSeparator = value.indexOf('@')
|
|
114
|
+
const rawName = variantSeparator === -1 ? value : value.slice(0, variantSeparator)
|
|
115
|
+
const rawVariant = variantSeparator === -1 ? '' : value.slice(variantSeparator + 1).trim()
|
|
116
|
+
const name = normalizeBlockName(rawName)
|
|
117
|
+
|
|
118
|
+
assertBlockName(name, label)
|
|
119
|
+
|
|
120
|
+
if (variantSeparator !== -1 && !rawVariant) {
|
|
121
|
+
throw new Error(`Invalid ${label}: "${value}"`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
name,
|
|
126
|
+
variant: rawVariant || undefined,
|
|
127
|
+
variantExplicit: variantSeparator !== -1,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
86
131
|
function toBlockKey(block) {
|
|
87
132
|
return `${block.name}@${block.variant}`
|
|
88
133
|
}
|
|
@@ -151,20 +196,14 @@ function loadProjectConfig(projectRoot) {
|
|
|
151
196
|
}
|
|
152
197
|
|
|
153
198
|
const blocks = blocksRaw.map((entry, index) => {
|
|
154
|
-
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
199
|
+
if (typeof entry !== 'string' && (!entry || typeof entry !== 'object' || Array.isArray(entry))) {
|
|
155
200
|
throw new Error(`Invalid block config entry at index ${index}`)
|
|
156
201
|
}
|
|
157
202
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (!variant) {
|
|
164
|
-
throw new Error(`Configured block "${name}" is missing a variant`)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return { name, variant }
|
|
203
|
+
return parseBlockSpecifier(entry, {
|
|
204
|
+
label: `block config entry at index ${index}`,
|
|
205
|
+
allowObject: true,
|
|
206
|
+
})
|
|
168
207
|
})
|
|
169
208
|
|
|
170
209
|
return { configPath, config, blocks: dedupeExplicitBlocks(blocks) }
|
|
@@ -173,7 +212,7 @@ function loadProjectConfig(projectRoot) {
|
|
|
173
212
|
function saveProjectConfig(configPath, config, blocks) {
|
|
174
213
|
const nextConfig = {
|
|
175
214
|
...config,
|
|
176
|
-
blocks,
|
|
215
|
+
blocks: blocks.map(block => block.variantExplicit ? `${block.name}@${block.variant}` : block.name),
|
|
177
216
|
}
|
|
178
217
|
|
|
179
218
|
fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`)
|
|
@@ -194,15 +233,25 @@ function normalizeRequestedBlockNames(rawNames, label = 'block name') {
|
|
|
194
233
|
throw new Error(`Missing ${label}`)
|
|
195
234
|
}
|
|
196
235
|
|
|
197
|
-
const normalizedNames = rawNames
|
|
198
|
-
|
|
236
|
+
const normalizedNames = rawNames
|
|
237
|
+
.map(rawName => parseBlockSpecifier(rawName, { label }).name)
|
|
199
238
|
|
|
200
|
-
|
|
239
|
+
return [...new Set(normalizedNames)]
|
|
240
|
+
}
|
|
201
241
|
|
|
202
|
-
|
|
203
|
-
|
|
242
|
+
function normalizeRequestedBlocks(rawNames, label = 'block name') {
|
|
243
|
+
if (!Array.isArray(rawNames) || rawNames.length === 0) {
|
|
244
|
+
throw new Error(`Missing ${label}`)
|
|
245
|
+
}
|
|
204
246
|
|
|
205
|
-
|
|
247
|
+
const blocks = rawNames.map(rawName => parseBlockSpecifier(rawName, { label }))
|
|
248
|
+
const byName = new Map()
|
|
249
|
+
|
|
250
|
+
for (const block of blocks) {
|
|
251
|
+
byName.set(block.name, block)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return [...byName.values()]
|
|
206
255
|
}
|
|
207
256
|
|
|
208
257
|
async function createMetaValidator(repository) {
|
|
@@ -488,16 +537,21 @@ export function createBlocksService({
|
|
|
488
537
|
}
|
|
489
538
|
|
|
490
539
|
async function addBlocks(rawNames) {
|
|
491
|
-
const
|
|
540
|
+
const requestedBlocks = normalizeRequestedBlocks(rawNames)
|
|
492
541
|
const rootBlocks = []
|
|
493
542
|
|
|
494
|
-
for (const
|
|
495
|
-
const meta = await getMeta(name)
|
|
543
|
+
for (const requestedBlock of requestedBlocks) {
|
|
544
|
+
const meta = await getMeta(requestedBlock.name)
|
|
496
545
|
|
|
497
546
|
rootBlocks.push({
|
|
498
|
-
name,
|
|
499
|
-
variant:
|
|
547
|
+
name: requestedBlock.name,
|
|
548
|
+
variant: requestedBlock.variant,
|
|
549
|
+
variantExplicit: requestedBlock.variantExplicit,
|
|
500
550
|
})
|
|
551
|
+
|
|
552
|
+
if (requestedBlock.variant && !meta.install?.variants?.[requestedBlock.variant]) {
|
|
553
|
+
throw new Error(`Variant "${requestedBlock.variant}" is not available for block "${requestedBlock.name}"`)
|
|
554
|
+
}
|
|
501
555
|
}
|
|
502
556
|
|
|
503
557
|
const plan = await resolveInstallPlan(rootBlocks)
|
|
@@ -506,12 +560,12 @@ export function createBlocksService({
|
|
|
506
560
|
|
|
507
561
|
const { configPath, config, blocks } = loadProjectConfig(projectRoot)
|
|
508
562
|
const nextBlocks = dedupeExplicitBlocks([
|
|
509
|
-
...blocks.filter(block => !
|
|
563
|
+
...blocks.filter(block => !requestedBlocks.some(requestedBlock => requestedBlock.name === block.name)),
|
|
510
564
|
...rootBlocks,
|
|
511
565
|
])
|
|
512
566
|
|
|
513
567
|
saveProjectConfig(configPath, config, nextBlocks)
|
|
514
|
-
logger.info(`added ${rootBlocks.map(block => `${block.name}@${block.variant}`).join(', ')}`)
|
|
568
|
+
logger.info(`added ${rootBlocks.map(block => block.variantExplicit ? `${block.name}@${block.variant}` : block.name).join(', ')}`)
|
|
515
569
|
|
|
516
570
|
return {
|
|
517
571
|
blocks: rootBlocks,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import childProcess from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import { styleText } from 'node:util'
|
|
4
|
+
import { packageRoot, resolveInside } from '../../utils.mjs'
|
|
5
|
+
|
|
6
|
+
function label(color, text) {
|
|
7
|
+
return styleText([color, 'bold'], text)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function printSkillsUsage() {
|
|
11
|
+
console.log([
|
|
12
|
+
styleText(['blue', 'bold'], 'newlogic skills'),
|
|
13
|
+
'',
|
|
14
|
+
styleText(['white', 'bold'], 'Usage:'),
|
|
15
|
+
'',
|
|
16
|
+
` ${styleText('green', 'newlogic skills install')} - Installs the bundled ${styleText('yellow', 'newlogic-cli')} skill`,
|
|
17
|
+
'',
|
|
18
|
+
styleText(['white', 'bold'], 'Notes:'),
|
|
19
|
+
'',
|
|
20
|
+
` ${styleText('dim', 'Resolves the installed CLI location automatically and runs:')}`,
|
|
21
|
+
` ${styleText('cyan', 'npx skills add <resolved-path>/skills/newlogic-cli')}`,
|
|
22
|
+
].join('\n'))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getBundledSkillPath(skillName = 'newlogic-cli') {
|
|
26
|
+
return resolveInside(packageRoot, 'skills', skillName)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function installBundledSkill(skillName = 'newlogic-cli', options = {}) {
|
|
30
|
+
const { execFileSync = childProcess.execFileSync } = options
|
|
31
|
+
const skillPath = getBundledSkillPath(skillName)
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(skillPath)) {
|
|
34
|
+
throw new Error(`Bundled skill "${skillName}" not found at ${skillPath}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
execFileSync('npx', ['skills', 'add', skillPath], { stdio: 'inherit' })
|
|
38
|
+
|
|
39
|
+
return skillPath
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { getBundledSkillPath, installBundledSkill }
|
|
43
|
+
|
|
44
|
+
export default async function skills(action) {
|
|
45
|
+
if (!action || action === 'help' || action === '--help') {
|
|
46
|
+
printSkillsUsage()
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
if (action === 'install') {
|
|
52
|
+
installBundledSkill()
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw new Error(`Unknown skills action "${action}"`)
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.log(`${label('red', 'error')} ${error.message}`)
|
|
60
|
+
process.exit(1)
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/utils.mjs
CHANGED
|
@@ -3,7 +3,8 @@ import fs from 'fs'
|
|
|
3
3
|
import path from 'path'
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
7
|
+
const { version, name } = JSON.parse(fs.readFileSync(path.resolve(packageRoot, 'package.json')).toString())
|
|
7
8
|
|
|
8
9
|
const execSync = (cmd) => {
|
|
9
10
|
try {
|
|
@@ -45,4 +46,4 @@ function resolveInside(rootDir, ...segments) {
|
|
|
45
46
|
return nextPath
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
export { execSync, stripIndent, resolveInside, version, name }
|
|
49
|
+
export { execSync, stripIndent, resolveInside, version, name, packageRoot }
|