@cellapp/cli 0.1.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/NOTICE +3 -0
- package/README.md +48 -0
- package/dist/packages/cli/src/client.js +134 -0
- package/dist/packages/cli/src/client.js.map +1 -0
- package/dist/packages/cli/src/deploy.js +108 -0
- package/dist/packages/cli/src/deploy.js.map +1 -0
- package/dist/packages/cli/src/main.js +78 -0
- package/dist/packages/cli/src/main.js.map +1 -0
- package/dist/packages/cli/src/origin.js +4 -0
- package/dist/packages/cli/src/origin.js.map +1 -0
- package/dist/packages/cli/src/project.js +26 -0
- package/dist/packages/cli/src/project.js.map +1 -0
- package/dist/packages/shared/src/artifacts.js +31 -0
- package/dist/packages/shared/src/artifacts.js.map +1 -0
- package/dist/packages/shared/src/errors.js +16 -0
- package/dist/packages/shared/src/errors.js.map +1 -0
- package/package.json +26 -0
- package/skills/cellapp-deploy/SKILL.md +22 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CellApp contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# CellApp CLI and Agent Skill
|
|
2
|
+
|
|
3
|
+
CellApp publishes static HTML applications from a local project. The CLI builds and uploads only the chosen static output directory. The Agent Skill guides an AI agent through the same process. A CellApp control service URL is required; the Skill does not supply one.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Requires Node.js 22.12 or later. Install the CLI from the published CellApp package when available:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g @cellapp/cli
|
|
11
|
+
cellapp --help
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Install the Agent Skill separately from this public GitHub repository:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npx skills add cellapp-ai/skills
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The installable Skill lives at [`skills/cellapp-deploy/SKILL.md`](skills/cellapp-deploy/SKILL.md). Installing the Skill does not install the CLI. Until the first package release, contributors can use `npm ci`, `npm run build`, and `npm run cli -- --help` from this repository.
|
|
21
|
+
|
|
22
|
+
## Deploy
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
cellapp login --origin https://control.example
|
|
26
|
+
cellapp deploy --project /absolute/project --output dist --build 'npm run build' --origin https://control.example
|
|
27
|
+
cellapp apps --origin https://control.example
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Plain HTML directories do not need `--build`. Use `--spa` only if the site has client-side routes. The first deployment returns the app URL and its share key; keep the share key private. `cellapp.json` stores the app association and pending deployment metadata without secrets. Credentials are stored in `~/.config/cellapp/` with private file permissions.
|
|
31
|
+
|
|
32
|
+
An existing `ohmyapp.json` and matching `~/.config/ohmyapp/` credential are copied safely on first use. The old files remain for recovery. `OHMYAPP_ORIGIN` and the `ohmyapp` command alias are supported during the transition; use `CELLAPP_ORIGIN` and `cellapp` for new setups.
|
|
33
|
+
|
|
34
|
+
The service hosts static content only. Frontend code and bundled values are readable by authorized visitors; never include private API credentials. Browser local data is not synchronized between devices.
|
|
35
|
+
|
|
36
|
+
## Development
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
npm ci
|
|
40
|
+
npm run typecheck
|
|
41
|
+
npm test
|
|
42
|
+
npm run build
|
|
43
|
+
npm run check:skill
|
|
44
|
+
npm run check:boundary
|
|
45
|
+
npm pack --dry-run --json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The [public HTTP contract](contracts/http-v1.json) describes the CLI and server boundary. The CLI and Skill are MIT licensed; see [LICENSE](LICENSE). Contributions follow [CONTRIBUTING.md](CONTRIBUTING.md), and security reports follow [SECURITY.md](SECURITY.md).
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { chmod, link, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
5
|
+
import { AppError } from '../../shared/src/errors.js';
|
|
6
|
+
export const randomKey = () => randomBytes(32).toString('hex');
|
|
7
|
+
export const requestID = () => randomBytes(16).toString('hex');
|
|
8
|
+
export async function saveJSON(path, value, privateFile = false) {
|
|
9
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
10
|
+
const temporary = `${path}.${requestID()}.tmp`;
|
|
11
|
+
await writeFile(temporary, JSON.stringify(value, null, 2) + '\n', { mode: privateFile ? 0o600 : 0o644, flag: 'wx' });
|
|
12
|
+
await rename(temporary, path);
|
|
13
|
+
if (privateFile)
|
|
14
|
+
await chmod(path, 0o600);
|
|
15
|
+
}
|
|
16
|
+
export async function readJSON(path) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
if (e.code === 'ENOENT')
|
|
22
|
+
return undefined;
|
|
23
|
+
throw e;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function saveJSONExclusive(path, value, privateFile = false) {
|
|
27
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
28
|
+
const temporary = `${path}.${requestID()}.tmp`;
|
|
29
|
+
try {
|
|
30
|
+
await writeFile(temporary, JSON.stringify(value, null, 2) + '\n', { mode: privateFile ? 0o600 : 0o644, flag: 'wx' });
|
|
31
|
+
await link(temporary, path);
|
|
32
|
+
if (privateFile)
|
|
33
|
+
await chmod(path, 0o600);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await unlink(temporary).catch((e) => { if (e.code !== 'ENOENT')
|
|
37
|
+
throw e; });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class Client {
|
|
41
|
+
origin;
|
|
42
|
+
credentialFile;
|
|
43
|
+
legacyCredentialFile;
|
|
44
|
+
token;
|
|
45
|
+
constructor(origin, configDirectory, legacyConfigDirectory) {
|
|
46
|
+
this.origin = origin;
|
|
47
|
+
const url = new URL(origin);
|
|
48
|
+
if (url.protocol !== 'https:' || url.origin !== origin)
|
|
49
|
+
throw new Error('The control origin must be an HTTPS origin');
|
|
50
|
+
const identity = createHash('sha256').update(origin).digest('hex').slice(0, 24);
|
|
51
|
+
this.credentialFile = join(configDirectory ?? join(homedir(), '.config', 'cellapp'), `${identity}.json`);
|
|
52
|
+
if (legacyConfigDirectory || configDirectory === undefined) {
|
|
53
|
+
this.legacyCredentialFile = join(legacyConfigDirectory ?? join(homedir(), '.config', 'ohmyapp'), `${identity}.json`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async load() {
|
|
57
|
+
const current = await readJSON(this.credentialFile);
|
|
58
|
+
if (current) {
|
|
59
|
+
this.token = current.token;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!this.legacyCredentialFile)
|
|
63
|
+
return;
|
|
64
|
+
const legacy = await readJSON(this.legacyCredentialFile);
|
|
65
|
+
if (!legacy)
|
|
66
|
+
return;
|
|
67
|
+
try {
|
|
68
|
+
await saveJSONExclusive(this.credentialFile, legacy, true);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
if (e.code !== 'EEXIST')
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
const migrated = await readJSON(this.credentialFile);
|
|
75
|
+
if (migrated?.token !== legacy.token)
|
|
76
|
+
throw new Error('CellApp credential conflicts with legacy credential; resolve the files manually');
|
|
77
|
+
this.token = migrated.token;
|
|
78
|
+
}
|
|
79
|
+
async request(path, method = 'GET', body, binary = false) {
|
|
80
|
+
const headers = {};
|
|
81
|
+
if (this.token)
|
|
82
|
+
headers.Authorization = `Bearer ${this.token}`;
|
|
83
|
+
if (body !== undefined)
|
|
84
|
+
headers['Content-Type'] = binary ? 'application/octet-stream' : 'application/json';
|
|
85
|
+
const response = await fetch(this.origin + path, { method, headers, redirect: 'error', body: body === undefined ? undefined : binary ? new Uint8Array(body) : JSON.stringify(body), signal: AbortSignal.timeout(120_000) });
|
|
86
|
+
const value = await response.json();
|
|
87
|
+
if (!response.ok)
|
|
88
|
+
throw new AppError(response.status, String(value.error), String(value.message), value.details);
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
async login(notify) {
|
|
92
|
+
const auth = await this.request('/device-authorizations', 'POST', {});
|
|
93
|
+
notify(`请在浏览器打开 ${auth.verificationUri}\n核对授权码:${auth.userCode}`);
|
|
94
|
+
const deadline = Date.now() + auth.expiresIn * 1000;
|
|
95
|
+
let interval = auth.interval * 1000;
|
|
96
|
+
while (Date.now() < deadline) {
|
|
97
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
98
|
+
try {
|
|
99
|
+
const credential = await this.request('/device-authorizations/poll', 'POST', { deviceSecret: auth.deviceSecret });
|
|
100
|
+
await saveJSON(this.credentialFile, { ...credential, origin: this.origin }, true);
|
|
101
|
+
this.token = credential.token;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
if (e instanceof AppError && e.code === 'authorization_pending')
|
|
106
|
+
continue;
|
|
107
|
+
if (e instanceof AppError && e.code === 'slow_down') {
|
|
108
|
+
interval += 5000;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
throw e;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
throw new AppError(400, 'expired_token', 'Authorization expired; run login again');
|
|
115
|
+
}
|
|
116
|
+
async logout() {
|
|
117
|
+
try {
|
|
118
|
+
if (this.token)
|
|
119
|
+
await this.request('/credentials/current', 'DELETE');
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
if (!(e instanceof AppError && e.status === 401))
|
|
123
|
+
throw e;
|
|
124
|
+
}
|
|
125
|
+
await unlink(this.credentialFile).catch((e) => { if (e.code !== 'ENOENT')
|
|
126
|
+
throw e; });
|
|
127
|
+
if (this.legacyCredentialFile) {
|
|
128
|
+
await unlink(this.legacyCredentialFile).catch((e) => { if (e.code !== 'ENOENT')
|
|
129
|
+
throw e; });
|
|
130
|
+
}
|
|
131
|
+
this.token = undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../../packages/cli/src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { lstat, readdir, readFile, realpath } from 'node:fs/promises';
|
|
2
|
+
import { resolve, relative, join, isAbsolute } from 'node:path';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { manifest, sha256, validPath } from '../../shared/src/artifacts.js';
|
|
5
|
+
import { AppError, ensure } from '../../shared/src/errors.js';
|
|
6
|
+
import { randomKey, requestID, saveJSON } from './client.js';
|
|
7
|
+
import { readProject } from './project.js';
|
|
8
|
+
export async function build(command, project) {
|
|
9
|
+
if (!command)
|
|
10
|
+
return;
|
|
11
|
+
await new Promise((resolve, reject) => {
|
|
12
|
+
const child = spawn(command, { cwd: project, shell: true, stdio: ['inherit', 'inherit', 'inherit'] });
|
|
13
|
+
child.on('error', reject);
|
|
14
|
+
child.on('exit', code => code === 0 ? resolve() : reject(new AppError(400, 'build_failed', `Build exited with code ${code}; nothing was uploaded`)));
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function collect(output, limits) {
|
|
18
|
+
ensure(!(await lstat(output)).isSymbolicLink(), 400, 'symlink', 'Output directory cannot be a symlink');
|
|
19
|
+
const root = await realpath(output);
|
|
20
|
+
const files = [];
|
|
21
|
+
async function visit(directory) {
|
|
22
|
+
for (const name of await readdir(directory)) {
|
|
23
|
+
const path = join(directory, name);
|
|
24
|
+
const rel = relative(root, path).split('\\').join('/');
|
|
25
|
+
validPath(rel);
|
|
26
|
+
const stat = await lstat(path);
|
|
27
|
+
ensure(!stat.isSymbolicLink(), 400, 'symlink', `Symlink not allowed: ${rel}`);
|
|
28
|
+
if (stat.isDirectory())
|
|
29
|
+
await visit(path);
|
|
30
|
+
else {
|
|
31
|
+
ensure(stat.isFile(), 400, 'invalid_file', `Only regular files are supported: ${rel}`);
|
|
32
|
+
ensure(stat.size <= limits.fileBytes, 413, 'file_limit', `File too large: ${rel}`);
|
|
33
|
+
ensure(files.length < limits.files, 413, 'file_limit', 'Too many files');
|
|
34
|
+
files.push({ path: rel, size: stat.size, sha256: sha256(await readFile(path)) });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
await visit(root);
|
|
39
|
+
return manifest(files, limits);
|
|
40
|
+
}
|
|
41
|
+
export async function deploy(client, directory, options, notify) {
|
|
42
|
+
const projectPath = resolve(directory);
|
|
43
|
+
const configPath = join(projectPath, 'cellapp.json');
|
|
44
|
+
let project = await readProject(projectPath);
|
|
45
|
+
if (project && project.origin !== client.origin)
|
|
46
|
+
throw new Error('Project belongs to another control origin');
|
|
47
|
+
const output = options.output ?? project?.output;
|
|
48
|
+
ensure(output, 400, 'output_required', 'Specify --output with the static build directory');
|
|
49
|
+
const outPath = resolve(projectPath, output);
|
|
50
|
+
const rel = relative(projectPath, outPath);
|
|
51
|
+
ensure(!isAbsolute(rel) && !rel.startsWith('..'), 400, 'invalid_output', 'Output must be inside the project');
|
|
52
|
+
const command = options.build ?? project?.build;
|
|
53
|
+
await build(command, projectPath);
|
|
54
|
+
const actualOutput = await realpath(outPath);
|
|
55
|
+
const actualProject = await realpath(projectPath);
|
|
56
|
+
const actualRelative = relative(actualProject, actualOutput);
|
|
57
|
+
ensure(!isAbsolute(actualRelative) && actualRelative !== '..' && !actualRelative.startsWith('../'), 400, 'invalid_output', 'Output resolves outside the project');
|
|
58
|
+
const limits = await client.request('/limits');
|
|
59
|
+
const files = await collect(outPath, limits);
|
|
60
|
+
project = { ...project, origin: client.origin, name: options.name ?? project?.name ?? 'My app', output, build: command, spa: options.spa ?? project?.spa ?? false, createRequestId: project?.createRequestId ?? requestID() };
|
|
61
|
+
let initialKey;
|
|
62
|
+
if (!project.appId) {
|
|
63
|
+
// The secret stays in memory; after a process crash the owner can list apps and
|
|
64
|
+
// explicitly link/reset the key. Never persist share keys in project metadata.
|
|
65
|
+
initialKey = randomKey();
|
|
66
|
+
const body = { name: project.name, key: initialKey, requestId: project.createRequestId };
|
|
67
|
+
const create = () => client.request('/apps', 'POST', body);
|
|
68
|
+
let app;
|
|
69
|
+
try {
|
|
70
|
+
app = await create();
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
if (e instanceof AppError)
|
|
74
|
+
throw e;
|
|
75
|
+
app = await create();
|
|
76
|
+
}
|
|
77
|
+
project.appId = app.id;
|
|
78
|
+
await saveJSON(configPath, project);
|
|
79
|
+
}
|
|
80
|
+
const apps = await client.request('/apps');
|
|
81
|
+
const app = apps.find(a => a.id === project.appId);
|
|
82
|
+
ensure(app, 404, 'app_not_found', 'Linked app was deleted or belongs to another owner');
|
|
83
|
+
const fingerprint = sha256(JSON.stringify({ files, spa: project.spa }));
|
|
84
|
+
if (project.pending && project.pending.fingerprint !== fingerprint)
|
|
85
|
+
project.pending = undefined;
|
|
86
|
+
project.pending ??= { requestId: requestID(), fingerprint, baseVersion: app.activeDeployment };
|
|
87
|
+
await saveJSON(configPath, project);
|
|
88
|
+
const base = `/apps/${app.id}/deployments`;
|
|
89
|
+
const d = await client.request(base, 'POST', { manifest: files, requestId: project.pending.requestId, baseVersion: project.pending.baseVersion, spa: project.spa });
|
|
90
|
+
const status = await client.request(`${base}/${d.id}`);
|
|
91
|
+
if (status.status !== 'published') {
|
|
92
|
+
for (const [index, file] of files.entries()) {
|
|
93
|
+
if (status.uploaded.includes(file.path))
|
|
94
|
+
continue;
|
|
95
|
+
const path = join(outPath, file.path);
|
|
96
|
+
ensure(!(await lstat(path)).isSymbolicLink(), 400, 'symlink', 'Artifact changed during deployment');
|
|
97
|
+
const data = await readFile(path);
|
|
98
|
+
ensure(sha256(data) === file.sha256, 400, 'artifact_changed', 'Artifact changed during deployment; rebuild and retry');
|
|
99
|
+
await client.request(`${base}/${d.id}/files/${index}`, 'PUT', data, true);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const result = await client.request(`${base}/${d.id}/publish`, 'POST', {});
|
|
103
|
+
delete project.pending;
|
|
104
|
+
await saveJSON(configPath, project);
|
|
105
|
+
notify('发布成功;浏览器本地数据不会跨设备同步。');
|
|
106
|
+
return { ...result, ...(initialKey ? { shareKey: initialKey } : { keyUnchanged: true }) };
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=deploy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../../../packages/cli/src/deploy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { resolve, join } from 'node:path';
|
|
4
|
+
import { Client, randomKey, readJSON, saveJSON } from './client.js';
|
|
5
|
+
import { deploy } from './deploy.js';
|
|
6
|
+
import { AppError } from '../../shared/src/errors.js';
|
|
7
|
+
import { readProject } from './project.js';
|
|
8
|
+
import { selectOrigin } from './origin.js';
|
|
9
|
+
const { values, positionals } = parseArgs({ allowPositionals: true, options: {
|
|
10
|
+
origin: { type: 'string' }, project: { type: 'string', default: '.' }, output: { type: 'string' },
|
|
11
|
+
build: { type: 'string' }, name: { type: 'string' }, spa: { type: 'boolean' }, help: { type: 'boolean' },
|
|
12
|
+
'config-dir': { type: 'string' },
|
|
13
|
+
} });
|
|
14
|
+
const [command, appId] = positionals;
|
|
15
|
+
const notify = (s) => process.stderr.write(s + '\n');
|
|
16
|
+
try {
|
|
17
|
+
if (values.help || !command) {
|
|
18
|
+
process.stdout.write('cellapp <login|logout|deploy|apps|link|reset-key|delete> [app-id] --origin https://control.example\nDeploy: --project . --output dist [--build "npm run build"] [--spa]\n');
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const projectPath = resolve(values.project);
|
|
22
|
+
const newProject = await readJSON(join(projectPath, 'cellapp.json'));
|
|
23
|
+
const legacyProject = await readJSON(join(projectPath, 'ohmyapp.json'));
|
|
24
|
+
const origin = selectOrigin(values.origin, newProject, legacyProject, process.env);
|
|
25
|
+
if (!origin)
|
|
26
|
+
throw new Error('Specify --origin or CELLAPP_ORIGIN');
|
|
27
|
+
const project = await readProject(projectPath);
|
|
28
|
+
const client = new Client(origin, values['config-dir']);
|
|
29
|
+
await client.load();
|
|
30
|
+
if (command === 'login') {
|
|
31
|
+
await client.login(notify);
|
|
32
|
+
process.stdout.write('{"authorized":true}\n');
|
|
33
|
+
}
|
|
34
|
+
else if (command === 'logout') {
|
|
35
|
+
await client.logout();
|
|
36
|
+
process.stdout.write('{"loggedOut":true}\n');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
if (!client.token)
|
|
40
|
+
await client.login(notify);
|
|
41
|
+
let result;
|
|
42
|
+
if (command === 'deploy')
|
|
43
|
+
result = await deploy(client, projectPath, values, notify);
|
|
44
|
+
else if (command === 'apps')
|
|
45
|
+
result = await client.request('/apps');
|
|
46
|
+
else {
|
|
47
|
+
if (!appId || !/^[a-f0-9]{32}$/.test(appId))
|
|
48
|
+
throw new Error('A valid app ID is required');
|
|
49
|
+
if (command === 'reset-key') {
|
|
50
|
+
const key = randomKey();
|
|
51
|
+
await client.request(`/apps/${appId}/key`, 'POST', { key });
|
|
52
|
+
result = { shareKey: key };
|
|
53
|
+
}
|
|
54
|
+
else if (command === 'delete')
|
|
55
|
+
result = await client.request(`/apps/${appId}`, 'DELETE');
|
|
56
|
+
else if (command === 'link') {
|
|
57
|
+
const apps = await client.request('/apps');
|
|
58
|
+
if (!apps.some(a => a.id === appId))
|
|
59
|
+
throw new Error('Application not owned by this credential');
|
|
60
|
+
if (!project)
|
|
61
|
+
throw new Error('Create project metadata with a deployment before linking');
|
|
62
|
+
project.appId = appId;
|
|
63
|
+
delete project.pending;
|
|
64
|
+
await saveJSON(join(projectPath, 'cellapp.json'), project);
|
|
65
|
+
result = { linked: appId };
|
|
66
|
+
}
|
|
67
|
+
else
|
|
68
|
+
throw new Error('Unknown command; use --help');
|
|
69
|
+
}
|
|
70
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
process.stderr.write(JSON.stringify(e instanceof AppError ? { error: e.code, message: e.message, details: e.details } : { error: 'cli_error', message: e.message }) + '\n');
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../../../packages/cli/src/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../../../../packages/cli/src/origin.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAAC,QAA4B,EAAE,OAA4B,EAAE,MAA2B,EAAE,WAA8B;IAClJ,OAAO,QAAQ,IAAI,OAAO,EAAE,MAAM,IAAI,WAAW,CAAC,cAAc,IAAI,MAAM,EAAE,MAAM,IAAI,WAAW,CAAC,cAAc,CAAC;AACnH,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readJSON, saveJSONExclusive } from './client.js';
|
|
3
|
+
export async function readProject(directory) {
|
|
4
|
+
const currentPath = join(directory, 'cellapp.json');
|
|
5
|
+
const legacyPath = join(directory, 'ohmyapp.json');
|
|
6
|
+
const current = await readJSON(currentPath);
|
|
7
|
+
const legacy = await readJSON(legacyPath);
|
|
8
|
+
if (current && legacy && (current.origin !== legacy.origin || (current.appId && legacy.appId && current.appId !== legacy.appId))) {
|
|
9
|
+
throw new Error('cellapp.json conflicts with ohmyapp.json; choose the correct project association manually');
|
|
10
|
+
}
|
|
11
|
+
if (current || !legacy)
|
|
12
|
+
return current;
|
|
13
|
+
try {
|
|
14
|
+
await saveJSONExclusive(currentPath, legacy);
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
if (e.code !== 'EEXIST')
|
|
18
|
+
throw e;
|
|
19
|
+
}
|
|
20
|
+
const migrated = await readJSON(currentPath);
|
|
21
|
+
if (JSON.stringify(migrated) !== JSON.stringify(legacy)) {
|
|
22
|
+
throw new Error('cellapp.json changed during migration; legacy ohmyapp.json was preserved');
|
|
23
|
+
}
|
|
24
|
+
return migrated;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../../../packages/cli/src/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAG1D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,SAAiB;IACjD,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAU,WAAW,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAU,UAAU,CAAC,CAAC;IACnD,IAAI,OAAO,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACjI,MAAM,IAAI,KAAK,CAAC,2FAA2F,CAAC,CAAC;IAC/G,CAAC;IACD,IAAI,OAAO,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC;IACvC,IAAI,CAAC;QAAC,MAAM,iBAAiB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAAC,CAAC;IACrD,OAAO,CAAC,EAAE,CAAC;QAAC,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,CAAC,CAAC;IAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAU,WAAW,CAAC,CAAC;IACtD,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { ensure } from './errors.js';
|
|
3
|
+
export const sha256 = (data) => createHash('sha256').update(data).digest('hex');
|
|
4
|
+
export function validPath(path) {
|
|
5
|
+
ensure(typeof path === 'string' && path.length > 0 && path.length <= 512, 400, 'invalid_path', 'Invalid artifact path');
|
|
6
|
+
ensure(!/[\\\x00-\x1f\x7f%?#]/.test(path) && !path.startsWith('/'), 400, 'invalid_path', 'Unsafe artifact path');
|
|
7
|
+
const parts = path.split('/');
|
|
8
|
+
ensure(parts.every(p => p && p !== '.' && p !== '..'), 400, 'invalid_path', 'Unsafe artifact path');
|
|
9
|
+
ensure(!parts.some(p => /^(?:\.|_hosting$|node_modules$|credentials(?:\.|$)|id_rsa$|id_ed25519$)/i.test(p)) && !/\.(pem|key|p12|pfx)$/i.test(path), 400, 'sensitive_path', 'Reserved or sensitive artifact path');
|
|
10
|
+
}
|
|
11
|
+
export function manifest(input, limits) {
|
|
12
|
+
ensure(Array.isArray(input) && input.length > 0 && input.length <= limits.files, 413, 'file_limit', 'Too many files or empty artifact');
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
let total = 0;
|
|
15
|
+
const files = input.map((item) => {
|
|
16
|
+
ensure(item && typeof item === 'object', 400, 'invalid_manifest', 'Invalid manifest entry');
|
|
17
|
+
const e = item;
|
|
18
|
+
ensure(Object.keys(e).every(k => ['path', 'size', 'sha256'].includes(k)), 400, 'invalid_manifest', 'Only regular files are supported');
|
|
19
|
+
validPath(e.path);
|
|
20
|
+
ensure(!seen.has(e.path), 400, 'duplicate_path', 'Duplicate artifact path');
|
|
21
|
+
seen.add(e.path);
|
|
22
|
+
ensure(typeof e.size === 'number' && Number.isSafeInteger(e.size) && e.size >= 0 && e.size <= limits.fileBytes, 413, 'file_limit', 'File too large');
|
|
23
|
+
ensure(typeof e.sha256 === 'string' && /^[a-f0-9]{64}$/.test(e.sha256), 400, 'invalid_digest', 'Invalid SHA-256');
|
|
24
|
+
total += e.size;
|
|
25
|
+
return { path: e.path, size: e.size, sha256: e.sha256 };
|
|
26
|
+
});
|
|
27
|
+
ensure(seen.has('index.html'), 400, 'static_export_required', 'A static index.html is required');
|
|
28
|
+
ensure(total <= limits.deploymentBytes, 413, 'deployment_limit', 'Deployment too large');
|
|
29
|
+
return files.sort((a, b) => Buffer.compare(Buffer.from(a.path), Buffer.from(b.path)));
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=artifacts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../../../packages/shared/src/artifacts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class AppError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
constructor(status, code, message, details) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function ensure(condition, status, code, message) {
|
|
13
|
+
if (!condition)
|
|
14
|
+
throw new AppError(status, code, message);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../../packages/shared/src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,QAAS,SAAQ,KAAK;IACd;IAAuB;IAAsC;IAAhF,YAAmB,MAAc,EAAS,IAAY,EAAE,OAAe,EAAS,OAAiC;QAC/G,KAAK,CAAC,OAAO,CAAC,CAAC;QADE,WAAM,GAAN,MAAM,CAAQ;QAAS,SAAI,GAAJ,IAAI,CAAQ;QAA0B,YAAO,GAAP,OAAO,CAA0B;IAEjH,CAAC;CACF;AACD,MAAM,UAAU,MAAM,CAAC,SAAkB,EAAE,MAAc,EAAE,IAAY,EAAE,OAAe;IACtF,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cellapp/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CellApp static application deployment CLI and Agent Skill",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {"access": "public", "registry": "https://registry.npmjs.org/"},
|
|
8
|
+
"workspaces": ["packages/cli", "packages/shared"],
|
|
9
|
+
"engines": {"node": ">=22.12.0"},
|
|
10
|
+
"bin": {"cellapp": "dist/packages/cli/src/main.js", "ohmyapp": "dist/packages/cli/src/main.js"},
|
|
11
|
+
"files": ["dist/packages", "skills/cellapp-deploy", "README.md", "LICENSE", "NOTICE"],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc -p tsconfig.json",
|
|
14
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
15
|
+
"test": "node --import tsx --test tests/*.test.ts",
|
|
16
|
+
"cli": "tsx packages/cli/src/main.ts",
|
|
17
|
+
"check:boundary": "node scripts/check-boundary.mjs",
|
|
18
|
+
"check:skill": "node scripts/check-skill.mjs",
|
|
19
|
+
"check:package": "node scripts/check-package.mjs"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "22.20.4",
|
|
23
|
+
"tsx": "4.23.15",
|
|
24
|
+
"typescript": "5.9.3"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cellapp-deploy
|
|
3
|
+
description: Publish or update a static HTML site or frontend tool on CellApp using local builds, device authorization, and an app-specific share key. Use when the user asks to deploy to CellApp; does not host backends or synchronize app data.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deploy a static application
|
|
7
|
+
|
|
8
|
+
Use the installed `cellapp` CLI. Installing this Skill does not install the CLI; if it is unavailable, tell the user to install the CellApp CLI using the public repository README. Do not substitute another hosting provider or invent a control URL. The control origin comes from the user's configuration, `CELLAPP_ORIGIN`, or an existing `cellapp.json`. Existing `ohmyapp.json` and `OHMYAPP_ORIGIN` are supported during migration.
|
|
9
|
+
|
|
10
|
+
Inspect the project's existing build command and static output directory. Reuse `cellapp.json` for updates. For a plain HTML directory, omit the build command. If the output directory or build command is ambiguous, ask only for the missing setting. A backend, SSR server, secret-dependent API, or database cannot be deployed with this skill; explain the static-export requirement before attempting publication.
|
|
11
|
+
|
|
12
|
+
Run `cellapp deploy --project <project> --output <output-directory> --build <existing-build-command> --origin <control-origin>`, omitting `--build` for plain HTML. Add `--spa` only when client-side page routing requires index fallback. Quote all paths and commands for the shell in use. Run user project builds under the agent's normal execution permissions.
|
|
13
|
+
|
|
14
|
+
The CLI starts device authorization automatically when no credential exists. Show the user the verification link and matching code, and keep the command running while they approve in a browser. Do not request account passwords or copy the deployment token into the conversation. Authorization denial/expiry stops the flow; let the user initiate another attempt.
|
|
15
|
+
|
|
16
|
+
Only report deployment success after the CLI returns `status: published`. Return the application URL and the initial app-specific share key to the requesting owner. For updates, report that the existing share key remains valid. Never put a key into a URL, repository, issue, or unrelated message. The key grants access to the application; it grants no management permissions.
|
|
17
|
+
|
|
18
|
+
On a transient upload/network error, retry the same deploy command once: saved pending metadata reuses the deployment operation. Stop after another failure and report the specific error. A version conflict requires refreshing the intended version before starting a new deployment; do not silently overwrite a concurrent update. For quota errors explain the configured limit and recovery action, without purchasing services or deleting apps automatically.
|
|
19
|
+
|
|
20
|
+
App management commands are `cellapp apps`, `cellapp reset-key <app-id>`, `cellapp delete <app-id>`, `cellapp login`, and `cellapp logout`. Resetting a key invalidates existing sessions. Use reset or delete only when requested; do not reset keys as an invisible retry strategy. If a first deployment's key is lost, explain that the owner can explicitly reset it.
|
|
21
|
+
|
|
22
|
+
Explain the relevant first-deployment limits briefly: frontend code and bundled values are readable by authorized visitors; never bundle private API credentials. Browser local data is not synchronized between devices. The service hosts static content only, with no user backend or offline Service Worker support.
|