workframe 0.1.0 → 0.1.1

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 (3) hide show
  1. package/README.md +11 -11
  2. package/bin/workframe.js +257 -257
  3. package/package.json +5 -1
package/README.md CHANGED
@@ -13,14 +13,14 @@ npx workframe setup
13
13
 
14
14
  ## Commands
15
15
 
16
- | Command | Purpose |
17
- |---------|---------|
18
- | `doctor` | Validate native-first layout, 4-service compose topology, manifest, and Docker runtime |
19
- | `setup` | Print onboarding steps from `Workframe/SETUP.md` |
20
-
21
- `doctor` is bootstrap-aware:
22
-
23
- - before Hermes setup, it validates scaffold/layout and skips runtime-only checks
24
- - after Hermes setup, it can validate compose/runtime expectations too
25
-
26
- Planned: `up`, `down`, `profiles list`, `add-agent`.
16
+ | Command | Purpose |
17
+ |---------|---------|
18
+ | `doctor` | Validate native-first layout, 4-service compose topology, manifest, and Docker runtime |
19
+ | `setup` | Print onboarding steps from `Workframe/SETUP.md` |
20
+
21
+ `doctor` is bootstrap-aware:
22
+
23
+ - before Hermes setup, it validates scaffold/layout and skips runtime-only checks
24
+ - after Hermes setup, it can validate compose/runtime expectations too
25
+
26
+ Planned: `up`, `down`, `profiles list`, `add-agent`.
package/bin/workframe.js CHANGED
@@ -1,257 +1,257 @@
1
- #!/usr/bin/env node
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { spawnSync } from 'node:child_process';
5
-
6
- function findProjectRoot(start = process.cwd()) {
7
- let dir = path.resolve(start);
8
- while (true) {
9
- if (fs.existsSync(path.join(dir, 'workframe-manifest.json'))) return { root: dir, manifestDir: dir };
10
- if (fs.existsSync(path.join(dir, 'Workframe', 'workframe-manifest.json'))) {
11
- return { root: dir, manifestDir: path.join(dir, 'Workframe') };
12
- }
13
- const parent = path.dirname(dir);
14
- if (parent === dir) return null;
15
- dir = parent;
16
- }
17
- }
18
-
19
- function icon(ok) {
20
- return ok ? '[ok]' : '[fail]';
21
- }
22
-
23
- function printCheck(name, ok, detail = '') {
24
- console.log(` ${icon(ok)} ${name}${detail ? `: ${detail}` : ''}`);
25
- return ok;
26
- }
27
-
28
- function printSkip(name, detail = '') {
29
- console.log(` [skip] ${name}${detail ? `: ${detail}` : ''}`);
30
- }
31
-
32
- function run(cmd, args, cwd) {
33
- const res = spawnSync(cmd, args, { encoding: 'utf8', cwd });
34
- return {
35
- ok: res.status === 0,
36
- code: res.status ?? 1,
37
- out: (res.stdout || '').trim(),
38
- err: (res.stderr || '').trim(),
39
- };
40
- }
41
-
42
- function readJson(file) {
43
- return JSON.parse(fs.readFileSync(file, 'utf8'));
44
- }
45
-
46
- function readText(file) {
47
- return fs.readFileSync(file, 'utf8');
48
- }
49
-
50
- function exists(root, rel) {
51
- return fs.existsSync(path.join(root, rel));
52
- }
53
-
54
- function relList(items) {
55
- return items.length ? items.join(', ') : 'none';
56
- }
57
-
58
- function countServices(composeText) {
59
- const lines = composeText.split(/\r?\n/);
60
- let inServices = false;
61
- let count = 0;
62
- for (const line of lines) {
63
- if (!inServices) {
64
- if (line.trim() === 'services:') inServices = true;
65
- continue;
66
- }
67
- if (/^[A-Za-z]/.test(line)) break;
68
- if (/^ [A-Za-z0-9_-]+:\s*$/.test(line)) count += 1;
69
- }
70
- return count;
71
- }
72
-
73
- function extractContainerNames(composeText) {
74
- return [...composeText.matchAll(/container_name:\s*([^\s]+)/g)].map((m) => m[1]);
75
- }
76
-
77
- function doctor() {
78
- const located = findProjectRoot();
79
- if (!located) {
80
- console.error('Not in a Workframe project (workframe-manifest.json not found).');
81
- process.exit(1);
82
- }
83
- const root = located.root;
84
- const manifestDir = located.manifestDir;
85
-
86
- console.log(`Workframe doctor - ${root}\n`);
87
-
88
- let pass = 0;
89
- let total = 0;
90
- const tally = (ok) => {
91
- total += 1;
92
- if (ok) pass += 1;
93
- return ok;
94
- };
95
-
96
- const manifestPath = path.join(manifestDir, 'workframe-manifest.json');
97
- let manifest = null;
98
- try {
99
- manifest = readJson(manifestPath);
100
- tally(printCheck('workframe-manifest.json', true));
101
- } catch (err) {
102
- tally(printCheck('workframe-manifest.json', false, `invalid JSON: ${err.message}`));
103
- console.log(`\n${pass}/${total} checks passed.`);
104
- process.exit(1);
105
- }
106
-
107
- const projectName = manifest.project_name || 'Unknown';
108
- const nativeSlug = manifest.native_agent?.profile_slug || 'workframe-agent';
109
- const nativeName = manifest.native_agent?.display_name || 'Workframe Agent';
110
- const stack = manifest.docker?.stack || path.basename(root).toLowerCase();
111
- const bootstrapDefault = manifest.bootstrap?.default || 'unknown';
112
- const referenceProfiles = manifest.profiles_pack_bootstrap || manifest.profiles || [];
113
- const installedAfterNative = manifest.profiles_installed_after_native_bootstrap || [];
114
- const specialistCatalog = manifest.profiles_catalog || [];
115
-
116
- console.log(` Project: ${projectName}`);
117
- console.log(` Native agent: ${nativeName} (${nativeSlug})`);
118
- console.log(` Pack: ${manifest.pack || 'unknown'}`);
119
- console.log(` Bootstrap default: ${bootstrapDefault}`);
120
- console.log(` Native bootstrap installs: ${relList(installedAfterNative)}`);
121
- console.log(` Specialist catalog: ${relList(specialistCatalog)}\n`);
122
-
123
- tally(printCheck('Agents/', exists(root, 'Agents')));
124
- tally(printCheck('Files/', exists(root, 'Files')));
125
- tally(printCheck('Files/AGENTS.md', exists(root, 'Files/AGENTS.md')));
126
- tally(printCheck('Files/.hermes.md', exists(root, 'Files/.hermes.md')));
127
- tally(printCheck('Workframe/SETUP.md', exists(root, 'Workframe/SETUP.md')));
128
- tally(printCheck('Workframe/docker-compose.yml', exists(root, 'Workframe/docker-compose.yml')));
129
- tally(printCheck('Workframe/scripts/agent-lifecycle.mjs', exists(root, 'Workframe/scripts/agent-lifecycle.mjs')));
130
- tally(printCheck('Workframe/scripts/lib/workframe-registry.mjs', exists(root, 'Workframe/scripts/lib/workframe-registry.mjs')));
131
-
132
- const nativeSeedSoul = exists(root, `Workframe/scripts/seed/profiles/${nativeSlug}/SOUL.md`);
133
- const nativeSeedSetup = exists(root, `Workframe/scripts/seed/profiles/${nativeSlug}/SETUP.md`);
134
- tally(printCheck('Native SOUL seed', nativeSeedSoul, nativeSeedSoul ? nativeSlug : 'missing native seed SOUL'));
135
- tally(printCheck('Native SETUP seed', nativeSeedSetup, nativeSeedSetup ? nativeSlug : 'missing native setup playbook'));
136
-
137
- tally(printCheck('Bootstrap default is native', bootstrapDefault === 'native', bootstrapDefault));
138
- tally(
139
- printCheck(
140
- 'Native-only installed-after-bootstrap set',
141
- installedAfterNative.length === 1 && installedAfterNative[0] === nativeSlug,
142
- relList(installedAfterNative),
143
- ),
144
- );
145
- tally(printCheck('Specialist catalog present', specialistCatalog.length > 0, relList(specialistCatalog)));
146
-
147
- const composePath = path.join(root, 'Workframe', 'docker-compose.yml');
148
- const composeText = exists(root, 'Workframe/docker-compose.yml') ? readText(composePath) : '';
149
- if (composeText) {
150
- tally(printCheck('Compose service count is 4', countServices(composeText) === 4, `${countServices(composeText)} services`));
151
- tally(printCheck('Compose mounts workframe-api', composeText.includes('./workframe-api/public:/app/public:ro')));
152
- tally(printCheck('Compose mounts Workframe UI', composeText.includes('WORKFRAME_UI_STATIC_DIR') || composeText.includes('./workframe-ui/public:/usr/share/nginx/html:ro')));
153
- tally(
154
- printCheck(
155
- 'Gateway uses native profile',
156
- composeText.includes(`"-p", "${nativeSlug}"`) || composeText.includes(`-p ${nativeSlug} gateway run`),
157
- nativeSlug,
158
- ),
159
- );
160
- tally(printCheck('Gateway enables dashboard TUI', composeText.includes('HERMES_DASHBOARD_TUI=1')));
161
- tally(printCheck('No profile dashboard services', !composeText.includes('dashboard-dev:') && !composeText.includes('/hermes-profiles/')));
162
- tally(printCheck('Gateway creates avatar/user dirs', composeText.includes('/workspace/User') && composeText.includes('/opt/data/Avatars')));
163
- const containers = extractContainerNames(composeText);
164
- tally(printCheck('Named core containers', containers.includes(`${stack}-gateway`) && containers.includes(`${stack}-dashboard`) && containers.includes(`${stack}-workframe-api`) && containers.includes(`${stack}-workframe`), relList(containers)));
165
- }
166
-
167
- const wfReadme = path.join(root, 'Workframe', 'README.md');
168
- if (fs.existsSync(wfReadme)) {
169
- const txt = readText(wfReadme);
170
- tally(printCheck('Project README references Workframe UI', txt.includes('Workframe UI')));
171
- }
172
-
173
- const dockerInfo = run('docker', ['info'], root);
174
- tally(printCheck('Docker daemon', dockerInfo.ok, dockerInfo.ok ? 'reachable' : (dockerInfo.err || 'not running')));
175
-
176
- if (dockerInfo.ok && composeText) {
177
- const agentsEnv = path.join(root, 'Agents', '.env');
178
- if (!fs.existsSync(agentsEnv)) {
179
- printSkip('docker compose config', 'run Hermes setup first to create Agents/.env');
180
- printSkip('Four compose containers running', 'stack not expected before bootstrap');
181
- printSkip('Compose containers healthy/running', 'stack not expected before bootstrap');
182
- } else {
183
- const composeConfig = run('docker', ['compose', '-f', composePath, 'config'], root);
184
- tally(printCheck('docker compose config', composeConfig.ok, composeConfig.ok ? 'valid' : composeConfig.err));
185
-
186
- const composePs = run('docker', ['compose', '-f', composePath, 'ps', '--format', 'json'], root);
187
- if (composePs.ok) {
188
- const rows = composePs.out
189
- .split(/\r?\n/)
190
- .map((line) => line.trim())
191
- .filter(Boolean)
192
- .map((line) => {
193
- try {
194
- return JSON.parse(line);
195
- } catch {
196
- return null;
197
- }
198
- })
199
- .filter(Boolean);
200
- const names = rows.map((row) => row.Name);
201
- tally(printCheck('Four compose containers running', rows.length === 4, relList(names)));
202
- const allRunning = rows.every((row) => String(row.State || '').toLowerCase() === 'running');
203
- tally(printCheck('Compose containers healthy/running', allRunning, rows.map((row) => `${row.Name}:${row.State}`).join(', ')));
204
- } else {
205
- tally(printCheck('docker compose ps', false, composePs.err || composePs.out));
206
- }
207
- }
208
- }
209
-
210
- console.log(`\n${pass}/${total} checks passed.`);
211
- if (pass < total) process.exit(1);
212
- }
213
-
214
- function setup() {
215
- const located = findProjectRoot();
216
- if (!located) {
217
- console.error('Not in a Workframe project.');
218
- process.exit(1);
219
- }
220
- const root = located.root;
221
- const setupDoc = path.join(root, 'Workframe', 'SETUP.md');
222
- if (fs.existsSync(setupDoc)) {
223
- console.log(fs.readFileSync(setupDoc, 'utf8'));
224
- } else {
225
- console.log('Open Workframe/SETUP.md for onboarding steps.');
226
- }
227
- }
228
-
229
- function usage() {
230
- console.log(`workframe - lifecycle CLI for existing Workframe projects
231
-
232
- Commands:
233
- workframe doctor Validate native-first layout, compose topology, and runtime state
234
- workframe setup Print onboarding steps from Workframe/SETUP.md
235
- workframe help Show this help
236
- `);
237
- }
238
-
239
- const [, , cmd] = process.argv;
240
- switch (cmd) {
241
- case 'doctor':
242
- doctor();
243
- break;
244
- case 'setup':
245
- setup();
246
- break;
247
- case 'help':
248
- case '--help':
249
- case '-h':
250
- case undefined:
251
- usage();
252
- break;
253
- default:
254
- console.error(`Unknown command: ${cmd}`);
255
- usage();
256
- process.exit(1);
257
- }
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ function findProjectRoot(start = process.cwd()) {
7
+ let dir = path.resolve(start);
8
+ while (true) {
9
+ if (fs.existsSync(path.join(dir, 'workframe-manifest.json'))) return { root: dir, manifestDir: dir };
10
+ if (fs.existsSync(path.join(dir, 'Workframe', 'workframe-manifest.json'))) {
11
+ return { root: dir, manifestDir: path.join(dir, 'Workframe') };
12
+ }
13
+ const parent = path.dirname(dir);
14
+ if (parent === dir) return null;
15
+ dir = parent;
16
+ }
17
+ }
18
+
19
+ function icon(ok) {
20
+ return ok ? '[ok]' : '[fail]';
21
+ }
22
+
23
+ function printCheck(name, ok, detail = '') {
24
+ console.log(` ${icon(ok)} ${name}${detail ? `: ${detail}` : ''}`);
25
+ return ok;
26
+ }
27
+
28
+ function printSkip(name, detail = '') {
29
+ console.log(` [skip] ${name}${detail ? `: ${detail}` : ''}`);
30
+ }
31
+
32
+ function run(cmd, args, cwd) {
33
+ const res = spawnSync(cmd, args, { encoding: 'utf8', cwd });
34
+ return {
35
+ ok: res.status === 0,
36
+ code: res.status ?? 1,
37
+ out: (res.stdout || '').trim(),
38
+ err: (res.stderr || '').trim(),
39
+ };
40
+ }
41
+
42
+ function readJson(file) {
43
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
44
+ }
45
+
46
+ function readText(file) {
47
+ return fs.readFileSync(file, 'utf8');
48
+ }
49
+
50
+ function exists(root, rel) {
51
+ return fs.existsSync(path.join(root, rel));
52
+ }
53
+
54
+ function relList(items) {
55
+ return items.length ? items.join(', ') : 'none';
56
+ }
57
+
58
+ function countServices(composeText) {
59
+ const lines = composeText.split(/\r?\n/);
60
+ let inServices = false;
61
+ let count = 0;
62
+ for (const line of lines) {
63
+ if (!inServices) {
64
+ if (line.trim() === 'services:') inServices = true;
65
+ continue;
66
+ }
67
+ if (/^[A-Za-z]/.test(line)) break;
68
+ if (/^ [A-Za-z0-9_-]+:\s*$/.test(line)) count += 1;
69
+ }
70
+ return count;
71
+ }
72
+
73
+ function extractContainerNames(composeText) {
74
+ return [...composeText.matchAll(/container_name:\s*([^\s]+)/g)].map((m) => m[1]);
75
+ }
76
+
77
+ function doctor() {
78
+ const located = findProjectRoot();
79
+ if (!located) {
80
+ console.error('Not in a Workframe project (workframe-manifest.json not found).');
81
+ process.exit(1);
82
+ }
83
+ const root = located.root;
84
+ const manifestDir = located.manifestDir;
85
+
86
+ console.log(`Workframe doctor - ${root}\n`);
87
+
88
+ let pass = 0;
89
+ let total = 0;
90
+ const tally = (ok) => {
91
+ total += 1;
92
+ if (ok) pass += 1;
93
+ return ok;
94
+ };
95
+
96
+ const manifestPath = path.join(manifestDir, 'workframe-manifest.json');
97
+ let manifest = null;
98
+ try {
99
+ manifest = readJson(manifestPath);
100
+ tally(printCheck('workframe-manifest.json', true));
101
+ } catch (err) {
102
+ tally(printCheck('workframe-manifest.json', false, `invalid JSON: ${err.message}`));
103
+ console.log(`\n${pass}/${total} checks passed.`);
104
+ process.exit(1);
105
+ }
106
+
107
+ const projectName = manifest.project_name || 'Unknown';
108
+ const nativeSlug = manifest.native_agent?.profile_slug || 'workframe-agent';
109
+ const nativeName = manifest.native_agent?.display_name || 'Workframe Agent';
110
+ const stack = manifest.docker?.stack || path.basename(root).toLowerCase();
111
+ const bootstrapDefault = manifest.bootstrap?.default || 'unknown';
112
+ const referenceProfiles = manifest.profiles_pack_bootstrap || manifest.profiles || [];
113
+ const installedAfterNative = manifest.profiles_installed_after_native_bootstrap || [];
114
+ const specialistCatalog = manifest.profiles_catalog || [];
115
+
116
+ console.log(` Project: ${projectName}`);
117
+ console.log(` Native agent: ${nativeName} (${nativeSlug})`);
118
+ console.log(` Pack: ${manifest.pack || 'unknown'}`);
119
+ console.log(` Bootstrap default: ${bootstrapDefault}`);
120
+ console.log(` Native bootstrap installs: ${relList(installedAfterNative)}`);
121
+ console.log(` Specialist catalog: ${relList(specialistCatalog)}\n`);
122
+
123
+ tally(printCheck('Agents/', exists(root, 'Agents')));
124
+ tally(printCheck('Files/', exists(root, 'Files')));
125
+ tally(printCheck('Files/AGENTS.md', exists(root, 'Files/AGENTS.md')));
126
+ tally(printCheck('Files/.hermes.md', exists(root, 'Files/.hermes.md')));
127
+ tally(printCheck('Workframe/SETUP.md', exists(root, 'Workframe/SETUP.md')));
128
+ tally(printCheck('Workframe/docker-compose.yml', exists(root, 'Workframe/docker-compose.yml')));
129
+ tally(printCheck('Workframe/scripts/agent-lifecycle.mjs', exists(root, 'Workframe/scripts/agent-lifecycle.mjs')));
130
+ tally(printCheck('Workframe/scripts/lib/workframe-registry.mjs', exists(root, 'Workframe/scripts/lib/workframe-registry.mjs')));
131
+
132
+ const nativeSeedSoul = exists(root, `Workframe/scripts/seed/profiles/${nativeSlug}/SOUL.md`);
133
+ const nativeSeedSetup = exists(root, `Workframe/scripts/seed/profiles/${nativeSlug}/SETUP.md`);
134
+ tally(printCheck('Native SOUL seed', nativeSeedSoul, nativeSeedSoul ? nativeSlug : 'missing native seed SOUL'));
135
+ tally(printCheck('Native SETUP seed', nativeSeedSetup, nativeSeedSetup ? nativeSlug : 'missing native setup playbook'));
136
+
137
+ tally(printCheck('Bootstrap default is native', bootstrapDefault === 'native', bootstrapDefault));
138
+ tally(
139
+ printCheck(
140
+ 'Native-only installed-after-bootstrap set',
141
+ installedAfterNative.length === 1 && installedAfterNative[0] === nativeSlug,
142
+ relList(installedAfterNative),
143
+ ),
144
+ );
145
+ tally(printCheck('Specialist catalog present', specialistCatalog.length > 0, relList(specialistCatalog)));
146
+
147
+ const composePath = path.join(root, 'Workframe', 'docker-compose.yml');
148
+ const composeText = exists(root, 'Workframe/docker-compose.yml') ? readText(composePath) : '';
149
+ if (composeText) {
150
+ tally(printCheck('Compose service count is 4', countServices(composeText) === 4, `${countServices(composeText)} services`));
151
+ tally(printCheck('Compose mounts workframe-api', composeText.includes('./workframe-api/public:/app/public:ro')));
152
+ tally(printCheck('Compose mounts Workframe UI', composeText.includes('WORKFRAME_UI_STATIC_DIR') || composeText.includes('./workframe-ui/public:/usr/share/nginx/html:ro')));
153
+ tally(
154
+ printCheck(
155
+ 'Gateway uses native profile',
156
+ composeText.includes(`"-p", "${nativeSlug}"`) || composeText.includes(`-p ${nativeSlug} gateway run`),
157
+ nativeSlug,
158
+ ),
159
+ );
160
+ tally(printCheck('Gateway enables dashboard TUI', composeText.includes('HERMES_DASHBOARD_TUI=1')));
161
+ tally(printCheck('No profile dashboard services', !composeText.includes('dashboard-dev:') && !composeText.includes('/hermes-profiles/')));
162
+ tally(printCheck('Gateway creates avatar/user dirs', composeText.includes('/workspace/User') && composeText.includes('/opt/data/Avatars')));
163
+ const containers = extractContainerNames(composeText);
164
+ tally(printCheck('Named core containers', containers.includes(`${stack}-gateway`) && containers.includes(`${stack}-dashboard`) && containers.includes(`${stack}-workframe-api`) && containers.includes(`${stack}-workframe`), relList(containers)));
165
+ }
166
+
167
+ const wfReadme = path.join(root, 'Workframe', 'README.md');
168
+ if (fs.existsSync(wfReadme)) {
169
+ const txt = readText(wfReadme);
170
+ tally(printCheck('Project README references Workframe UI', txt.includes('Workframe UI')));
171
+ }
172
+
173
+ const dockerInfo = run('docker', ['info'], root);
174
+ tally(printCheck('Docker daemon', dockerInfo.ok, dockerInfo.ok ? 'reachable' : (dockerInfo.err || 'not running')));
175
+
176
+ if (dockerInfo.ok && composeText) {
177
+ const agentsEnv = path.join(root, 'Agents', '.env');
178
+ if (!fs.existsSync(agentsEnv)) {
179
+ printSkip('docker compose config', 'run Hermes setup first to create Agents/.env');
180
+ printSkip('Four compose containers running', 'stack not expected before bootstrap');
181
+ printSkip('Compose containers healthy/running', 'stack not expected before bootstrap');
182
+ } else {
183
+ const composeConfig = run('docker', ['compose', '-f', composePath, 'config'], root);
184
+ tally(printCheck('docker compose config', composeConfig.ok, composeConfig.ok ? 'valid' : composeConfig.err));
185
+
186
+ const composePs = run('docker', ['compose', '-f', composePath, 'ps', '--format', 'json'], root);
187
+ if (composePs.ok) {
188
+ const rows = composePs.out
189
+ .split(/\r?\n/)
190
+ .map((line) => line.trim())
191
+ .filter(Boolean)
192
+ .map((line) => {
193
+ try {
194
+ return JSON.parse(line);
195
+ } catch {
196
+ return null;
197
+ }
198
+ })
199
+ .filter(Boolean);
200
+ const names = rows.map((row) => row.Name);
201
+ tally(printCheck('Four compose containers running', rows.length === 4, relList(names)));
202
+ const allRunning = rows.every((row) => String(row.State || '').toLowerCase() === 'running');
203
+ tally(printCheck('Compose containers healthy/running', allRunning, rows.map((row) => `${row.Name}:${row.State}`).join(', ')));
204
+ } else {
205
+ tally(printCheck('docker compose ps', false, composePs.err || composePs.out));
206
+ }
207
+ }
208
+ }
209
+
210
+ console.log(`\n${pass}/${total} checks passed.`);
211
+ if (pass < total) process.exit(1);
212
+ }
213
+
214
+ function setup() {
215
+ const located = findProjectRoot();
216
+ if (!located) {
217
+ console.error('Not in a Workframe project.');
218
+ process.exit(1);
219
+ }
220
+ const root = located.root;
221
+ const setupDoc = path.join(root, 'Workframe', 'SETUP.md');
222
+ if (fs.existsSync(setupDoc)) {
223
+ console.log(fs.readFileSync(setupDoc, 'utf8'));
224
+ } else {
225
+ console.log('Open Workframe/SETUP.md for onboarding steps.');
226
+ }
227
+ }
228
+
229
+ function usage() {
230
+ console.log(`workframe - lifecycle CLI for existing Workframe projects
231
+
232
+ Commands:
233
+ workframe doctor Validate native-first layout, compose topology, and runtime state
234
+ workframe setup Print onboarding steps from Workframe/SETUP.md
235
+ workframe help Show this help
236
+ `);
237
+ }
238
+
239
+ const [, , cmd] = process.argv;
240
+ switch (cmd) {
241
+ case 'doctor':
242
+ doctor();
243
+ break;
244
+ case 'setup':
245
+ setup();
246
+ break;
247
+ case 'help':
248
+ case '--help':
249
+ case '-h':
250
+ case undefined:
251
+ usage();
252
+ break;
253
+ default:
254
+ console.error(`Unknown command: ${cmd}`);
255
+ usage();
256
+ process.exit(1);
257
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workframe",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Lifecycle CLI for Workframe projects (doctor, setup, up/down)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,6 +16,10 @@
16
16
  "node": ">=20"
17
17
  },
18
18
  "license": "Apache-2.0",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/npx-workframe/workframe.git"
22
+ },
19
23
  "keywords": [
20
24
  "workframe",
21
25
  "hermes",