costaff-workspace 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Simon Liu
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/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # costaff-workspace
2
+
3
+ Publish documents, decks and workbooks to [CoStaff Workspace](https://workspace.costaffs.app),
4
+ and pull their source back out to keep working on them.
5
+
6
+ Built for projects made with [open-doc](https://github.com/simonliu-ai-product/open-doc),
7
+ open-slide and open-sheet.
8
+
9
+ ```bash
10
+ npm i -g costaff-workspace
11
+
12
+ cd my-doc
13
+ costaff-workspace push
14
+ ```
15
+
16
+ That is the whole command. In a project folder it works the rest out: the folder
17
+ name is the slug, `package.json` says whether this is a document, a deck or a
18
+ workbook, `dist/` is the built bundle, and the source travels with it so the file
19
+ can be pulled back and edited. Every guess is printed, and a guess it cannot make
20
+ stops the push rather than inventing something.
21
+
22
+ The first push shows a device code to authorise in a browser. That machine stays
23
+ signed in afterwards.
24
+
25
+ ## Commands
26
+
27
+ ```bash
28
+ costaff-workspace push # push this folder
29
+ costaff-workspace pull <token> [dir] # fetch a published file's source
30
+ costaff-workspace login # sign this machine in
31
+ costaff-workspace logout # forget this machine's sign-in
32
+ ```
33
+
34
+ Useful flags on `push`:
35
+
36
+ | Flag | |
37
+ | --- | --- |
38
+ | `--slug`, `--kind`, `--title` | override a guess |
39
+ | `--site-dir <dir>` | the built bundle (default `dist`) |
40
+ | `--no-source` | publish the bundle alone — nothing can be pulled back out of it |
41
+ | `--dry-run` | package and report, without uploading or needing a network |
42
+ | `--endpoint <url>` | a receiver you run yourself |
43
+
44
+ ## A project is a site of many documents
45
+
46
+ An open-doc project is not one file — it is a site holding several documents
47
+ under `docs/`, and one `open-doc build` puts all of their chunks in a single
48
+ `assets/` directory. So `push` builds each document into a bundle of its own and
49
+ pushes them one at a time.
50
+
51
+ That is not tidiness. A share can only withhold the rest of a workspace if the
52
+ rest of the workspace is not in the bundle: inside a shared build, anyone holding
53
+ a link to one document can fetch the chunks of the others. The cost is real — N
54
+ builds instead of one, and assets repeated across them.
55
+
56
+ ## The protocol is public
57
+
58
+ `src/protocol.ts` is a specification, not an implementation detail. Anyone can
59
+ run their own receiver and point `--endpoint` at it; `COSTAFF_WORKSPACE_ENDPOINT`
60
+ does the same thing for a whole shell.
61
+
62
+ Adding a required field is a breaking change for every third-party receiver, so
63
+ optional is the default. `source` is optional: a push without it is still valid,
64
+ and a receiver that ignores it still serves. `pull` is optional in the discovery
65
+ document — a receiver that cannot hand source back omits the field rather than
66
+ answering 404 to a command the CLI thought it had.
67
+
68
+ ## What travels with the source
69
+
70
+ `node_modules` and dotfiles are not collected. `.env` is inside that.
71
+
72
+ Local dependency specs (`link:`, `file:`, `workspace:`, `portal:`) are replaced
73
+ with the version actually installed, because a path on the author's disk exists
74
+ on no other machine. A version that cannot be resolved refuses the push — a
75
+ published path nobody can resolve buys an install failure that talks about a
76
+ missing directory instead of a broken dependency.
77
+
78
+ ## What comes back depends on who you are
79
+
80
+ The server decides, not the client.
81
+
82
+ | | You get | Pushing it back |
83
+ | --- | --- | --- |
84
+ | Owner | includes the token | updates the same link |
85
+ | Shared with you | no token | publishes at a new link of your own; the original is untouched |
86
+
87
+ Two clients each deciding who owns a file would eventually disagree.
88
+
89
+ ## Licence
90
+
91
+ MIT
package/dist/bundle.js ADDED
@@ -0,0 +1,83 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { PUSH_PROTOCOL } from "./protocol.js";
4
+ async function collect(dir, rel = '') {
5
+ const out = new Map();
6
+ for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
7
+ const abs = path.join(dir, entry.name);
8
+ const key = rel ? `${rel}/${entry.name}` : entry.name;
9
+ if (entry.isDirectory()) {
10
+ for (const [k, v] of await collect(abs, key))
11
+ out.set(k, v);
12
+ }
13
+ else if (entry.isFile()) {
14
+ out.set(key, new Uint8Array(await fs.readFile(abs)));
15
+ }
16
+ }
17
+ return out;
18
+ }
19
+ function nest(files) {
20
+ const tree = {};
21
+ for (const [key, bytes] of files) {
22
+ const parts = key.split('/');
23
+ let node = tree;
24
+ for (const part of parts.slice(0, -1)) {
25
+ const next = node[part];
26
+ if (next === undefined || next instanceof Uint8Array) {
27
+ const created = {};
28
+ node[part] = created;
29
+ node = created;
30
+ }
31
+ else {
32
+ node = next;
33
+ }
34
+ }
35
+ node[parts[parts.length - 1]] = bytes;
36
+ }
37
+ return tree;
38
+ }
39
+ /**
40
+ * Zips one file's built site plus its manifest.
41
+ *
42
+ * The manifest travels inside the zip rather than in a header so a receiver
43
+ * never has to parse metadata out of a size-capped header, and so the archive
44
+ * is self-describing once it lands in storage.
45
+ *
46
+ * Nothing here inspects the site: what the manifest says is what the caller
47
+ * stated. A publisher that guessed would sooner or later name something the
48
+ * recipient was never given.
49
+ */
50
+ export async function createBundle(input) {
51
+ const stat = await fs.stat(input.siteDir).catch(() => null);
52
+ if (stat === null || !stat.isDirectory()) {
53
+ throw new Error(`No site at ${input.siteDir} — build it first.`);
54
+ }
55
+ const files = await collect(input.siteDir);
56
+ const entry = input.manifest.entry;
57
+ if (!files.has(entry)) {
58
+ throw new Error(`${input.siteDir} has no ${entry} — it is not a publishable site.`);
59
+ }
60
+ const manifest = {
61
+ ...input.manifest,
62
+ protocol: PUSH_PROTOCOL,
63
+ pushedAt: input.pushedAt,
64
+ ...(input.source === undefined
65
+ ? {}
66
+ : {
67
+ source: {
68
+ dir: input.source.dir,
69
+ entry: input.source.entry,
70
+ dependencies: input.source.dependencies,
71
+ devDependencies: input.source.devDependencies,
72
+ },
73
+ }),
74
+ };
75
+ const { zipSync } = await import('fflate');
76
+ const tree = {
77
+ 'manifest.json': new TextEncoder().encode(`${JSON.stringify(manifest, null, 2)}\n`),
78
+ site: nest(files),
79
+ ...(input.source === undefined ? {} : { source: nest(input.source.files) }),
80
+ };
81
+ return { bytes: zipSync(tree), manifest };
82
+ }
83
+ //# sourceMappingURL=bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { PublishError, discover } from "./client.js";
2
+ import { DEFAULT_ENDPOINT } from "./defaults.js";
3
+ import { pull } from "./pull.js";
4
+ import { login } from "./push.js";
5
+ const USAGE = `costaff-workspace pull — fetch a published file's source back out
6
+
7
+ costaff-workspace pull <token> [dir] [options]
8
+
9
+ --endpoint <url> receiver (default ${DEFAULT_ENDPOINT},
10
+ or COSTAFF_WORKSPACE_ENDPOINT)
11
+ --force write into a directory that is not empty
12
+ --bearer <token> auth token instead of a stored login
13
+
14
+ If you own the file, what comes back can be pushed straight back to the
15
+ same link. If it was shared with you, it comes back as a copy: pushing it
16
+ publishes at a new link under your own workspace.
17
+ `;
18
+ export async function runPull(argv) {
19
+ if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
20
+ process.stdout.write(USAGE);
21
+ return;
22
+ }
23
+ const flags = {};
24
+ const positional = [];
25
+ for (let i = 0; i < argv.length; i += 1) {
26
+ const arg = argv[i];
27
+ if (!arg.startsWith('--')) {
28
+ positional.push(arg);
29
+ continue;
30
+ }
31
+ const next = argv[i + 1];
32
+ if (next !== undefined && !next.startsWith('--')) {
33
+ flags[arg.slice(2)] = next;
34
+ i += 1;
35
+ }
36
+ else {
37
+ flags[arg.slice(2)] = true;
38
+ }
39
+ }
40
+ const str = (k) => typeof flags[k] === 'string' ? flags[k] : undefined;
41
+ const endpoint = str('endpoint') ?? process.env.COSTAFF_WORKSPACE_ENDPOINT ?? DEFAULT_ENDPOINT;
42
+ if (endpoint === '')
43
+ throw new PublishError('missing --endpoint');
44
+ const token = positional[0];
45
+ if (token === undefined)
46
+ throw new PublishError('missing <token>');
47
+ const out = (line) => {
48
+ process.stdout.write(line);
49
+ };
50
+ await pull({
51
+ endpoint,
52
+ token,
53
+ dir: positional[1],
54
+ force: flags.force === true,
55
+ bearer: str('bearer'),
56
+ out,
57
+ signIn: async () => login({ endpoint, out }, (await discover(endpoint)).name),
58
+ });
59
+ }
60
+ //# sourceMappingURL=cli-pull.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-pull.js","sourceRoot":"","sources":["../src/cli-pull.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAElC,MAAM,KAAK,GAAG;;;;2CAI6B,gBAAgB;;;;;;;;CAQ1D,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAc;IAC1C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAqC,EAAE,CAAC;IACnD,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACrB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;YAC3B,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAsB,EAAE,CAC5C,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAK,CAAC,CAAC,CAAY,CAAC,CAAC,CAAC,SAAS,CAAC;IAElE,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,gBAAgB,CAAC;IAC/F,IAAI,QAAQ,KAAK,EAAE;QAAE,MAAM,IAAI,YAAY,CAAC,oBAAoB,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,YAAY,CAAC,iBAAiB,CAAC,CAAC;IAEnE,MAAM,GAAG,GAAG,CAAC,IAAY,EAAQ,EAAE;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC;IACF,MAAM,IAAI,CAAC;QACT,QAAQ;QACR,KAAK;QACL,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;QAClB,KAAK,EAAE,KAAK,CAAC,KAAK,KAAK,IAAI;QAC3B,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC;QACrB,GAAG;QACH,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;KAC9E,CAAC,CAAC;AACL,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,173 @@
1
+ import { PublishError } from "./client.js";
2
+ import { DEFAULT_ENDPOINT, kindFromManifest, readManifest, slugFromDir, titleFromManifest, } from "./defaults.js";
3
+ import { pushProject, realBuildItem, scanProject } from "./project.js";
4
+ import { push } from "./push.js";
5
+ const USAGE = `costaff-workspace push — push one built file into your workspace
6
+
7
+ costaff-workspace push [options]
8
+
9
+ Run it in the project folder with nothing else and it works out the rest:
10
+ the folder name is the slug, package.json says which kind, dist/ is the
11
+ bundle, and the source travels with it so it can be pulled back and edited.
12
+
13
+ --endpoint <url> receiver (default ${DEFAULT_ENDPOINT},
14
+ or COSTAFF_WORKSPACE_ENDPOINT)
15
+ --slug <slug> the file's identity (default: the folder's name)
16
+ --kind <kind> document | workbook | deck (default: read from
17
+ package.json, else document)
18
+ --site-dir <dir> built bundle to upload (default dist)
19
+ --title <text> display name (default: package.json description,
20
+ else the slug)
21
+ --subtitle <text>
22
+ --folder <name> folder in the file manager
23
+ --route <path> route inside the bundle (default /)
24
+ --entry <file> entry file inside the bundle (default index.html)
25
+ --source-dir <dir> the item's source, relative to --source-root
26
+ (default: the whole project)
27
+ --source-root <dir> workspace root the source and its build setup live in
28
+ (default: the current directory)
29
+ --no-source publish the built bundle alone; nothing can be pulled
30
+ back out of it afterwards
31
+ --source-entry <f> file a rebuild starts from (default index.tsx)
32
+ --token <token> override the remembered public address
33
+ --bearer <token> auth token instead of a stored login
34
+ --login sign in again even if a token is stored;
35
+ on its own (with --endpoint) it just signs in
36
+ --logout forget the stored login for this endpoint
37
+ --dry-run package and report, do not upload
38
+ `;
39
+ const KINDS = new Set(['document', 'workbook', 'deck']);
40
+ function parse(argv) {
41
+ const flags = {};
42
+ for (let i = 0; i < argv.length; i += 1) {
43
+ const arg = argv[i];
44
+ if (!arg.startsWith('--'))
45
+ continue;
46
+ const name = arg.slice(2);
47
+ const next = argv[i + 1];
48
+ if (next !== undefined && !next.startsWith('--')) {
49
+ flags[name] = next;
50
+ i += 1;
51
+ }
52
+ else {
53
+ flags[name] = true;
54
+ }
55
+ }
56
+ const str = (k) => typeof flags[k] === 'string' ? flags[k] : undefined;
57
+ const kind = (str('kind') ?? 'document');
58
+ if (!KINDS.has(kind))
59
+ throw new PublishError(`--kind must be one of ${[...KINDS].join(', ')}`);
60
+ const slug = str('slug') ?? '';
61
+ return {
62
+ help: flags.help === true || flags.h === true,
63
+ /* 猜測要能被說出來,所以記下哪些是使用者自己給的。 */
64
+ given: {
65
+ slug: str('slug') !== undefined,
66
+ kind: str('kind') !== undefined,
67
+ title: str('title') !== undefined,
68
+ },
69
+ noSource: flags['no-source'] === true,
70
+ endpoint: str('endpoint') ?? process.env.COSTAFF_WORKSPACE_ENDPOINT ?? DEFAULT_ENDPOINT,
71
+ slug,
72
+ kind,
73
+ title: str('title') ?? slug,
74
+ subtitle: str('subtitle'),
75
+ folder: str('folder') ?? null,
76
+ route: str('route') ?? '/',
77
+ entry: str('entry'),
78
+ siteDir: str('site-dir') ?? 'dist',
79
+ sourceRoot: str('source-root') ?? process.cwd(),
80
+ sourceDir: str('source-dir'),
81
+ sourceEntry: str('source-entry'),
82
+ token: str('token'),
83
+ bearer: str('bearer'),
84
+ login: flags.login === true,
85
+ logout: flags.logout === true,
86
+ dryRun: flags['dry-run'] === true,
87
+ out: (line) => process.stdout.write(line),
88
+ };
89
+ }
90
+ /*
91
+ * 補上沒給的值,並且把每一個猜測印出來。安靜猜錯比要求人打完整指令更糟 —— 推錯
92
+ * 位置的人得看得出來是哪一個猜測害的。
93
+ */
94
+ async function fill(opts) {
95
+ const root = opts.sourceRoot ?? process.cwd();
96
+ const guessed = [];
97
+ if (!opts.given.slug) {
98
+ const slug = slugFromDir(root);
99
+ if (slug === null) {
100
+ throw new PublishError(`cannot make a slug out of the folder name — pass --slug (lowercase letters, digits and dashes)`);
101
+ }
102
+ opts.slug = slug;
103
+ guessed.push(`--slug ${slug}`);
104
+ }
105
+ const manifest = await readManifest(root);
106
+ if (!opts.given.kind) {
107
+ const kind = kindFromManifest(manifest);
108
+ if (kind !== null) {
109
+ opts.kind = kind;
110
+ guessed.push(`--kind ${kind}`);
111
+ }
112
+ }
113
+ if (!opts.given.title) {
114
+ opts.title = titleFromManifest(manifest, opts.slug);
115
+ }
116
+ /*
117
+ * 原始碼預設跟著走:沒有它,推上去的東西之後拉不回來改,而那正是這個服務
118
+ * 存在的理由。--no-source 是明確的退出方式。
119
+ */
120
+ if (opts.noSource) {
121
+ opts.sourceRoot = undefined;
122
+ opts.sourceDir = undefined;
123
+ }
124
+ else if (opts.sourceDir === undefined) {
125
+ opts.sourceDir = '.';
126
+ guessed.push('--source-dir .');
127
+ }
128
+ if (guessed.length > 0)
129
+ opts.out?.(` guessed ${guessed.join(' ')}\n`);
130
+ }
131
+ export async function runPush(argv) {
132
+ const opts = parse(argv);
133
+ if (opts.help) {
134
+ process.stdout.write(USAGE);
135
+ return;
136
+ }
137
+ if (!opts.logout && opts.endpoint === '') {
138
+ process.stderr.write(`error: missing --endpoint\n\n${USAGE}`);
139
+ process.exit(1);
140
+ }
141
+ if (opts.logout || opts.login) {
142
+ await push(opts);
143
+ return;
144
+ }
145
+ /*
146
+ * 一個 open-doc/open-slide/open-sheet 專案是一個站台含多份文件,不是一份
147
+ * 檔案。每一份各自建置、各自推 —— 共用的 bundle 裡,拿到其中一份連結的人就能
148
+ * 抓到其餘的 chunk,那樣分享設定擋不住任何東西。
149
+ *
150
+ * 明確給了 --slug 的人是在手動推某一份,照他說的做。
151
+ */
152
+ const root = opts.sourceRoot ?? process.cwd();
153
+ const items = opts.given.slug ? [] : await scanProject(root);
154
+ if (items.length > 0) {
155
+ const by = new Map();
156
+ for (const it of items)
157
+ by.set(it.surface.label, (by.get(it.surface.label) ?? 0) + 1);
158
+ const shape = [...by].map(([label, n]) => `${n} ${label}`).join('、');
159
+ process.stdout.write(` ${shape} —— 各自建置成獨立的 bundle\n`);
160
+ await pushProject({
161
+ root,
162
+ endpoint: opts.endpoint,
163
+ items,
164
+ dryRun: opts.dryRun,
165
+ buildItem: realBuildItem,
166
+ base: { bearer: opts.bearer, folder: opts.folder },
167
+ });
168
+ return;
169
+ }
170
+ await fill(opts);
171
+ await push(opts);
172
+ }
173
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/client.js ADDED
@@ -0,0 +1,123 @@
1
+ import { DISCOVERY_PATH, isDiscovery, isPushResult, PUSH_PROTOCOL, } from "./protocol.js";
2
+ export class PublishError extends Error {
3
+ status;
4
+ code;
5
+ constructor(message, status,
6
+ /** Machine-readable reason from the receiver; a status alone is ambiguous. */
7
+ code) {
8
+ super(message);
9
+ this.status = status;
10
+ this.code = code;
11
+ this.name = 'PublishError';
12
+ }
13
+ }
14
+ function resolveAgainst(endpoint, target) {
15
+ return new URL(target, endpoint.endsWith('/') ? endpoint : `${endpoint}/`).toString();
16
+ }
17
+ async function readJson(res) {
18
+ const text = await res.text();
19
+ if (text.trim() === '')
20
+ return null;
21
+ try {
22
+ return JSON.parse(text);
23
+ }
24
+ catch {
25
+ throw new PublishError(`${res.url} returned ${res.status} with a non-JSON body.`, res.status);
26
+ }
27
+ }
28
+ function errorCode(body) {
29
+ if (typeof body === 'object' && body !== null) {
30
+ const code = body.code;
31
+ if (typeof code === 'string' && code !== '')
32
+ return code;
33
+ }
34
+ return undefined;
35
+ }
36
+ function errorMessage(body, fallback) {
37
+ if (typeof body === 'object' && body !== null) {
38
+ const message = body.message;
39
+ if (typeof message === 'string' && message.trim() !== '')
40
+ return message;
41
+ }
42
+ return fallback;
43
+ }
44
+ export async function discover(endpoint) {
45
+ const url = resolveAgainst(endpoint, `.${DISCOVERY_PATH}`);
46
+ let res;
47
+ try {
48
+ res = await fetch(url, { headers: { accept: 'application/json' } });
49
+ }
50
+ catch (err) {
51
+ throw new PublishError(`Cannot reach ${endpoint} — ${err instanceof Error ? err.message : String(err)}`);
52
+ }
53
+ if (!res.ok) {
54
+ throw new PublishError(`${endpoint} is not a CoStaff Workspace endpoint (${res.status}).`);
55
+ }
56
+ const body = await readJson(res);
57
+ if (!isDiscovery(body)) {
58
+ throw new PublishError(`${endpoint} returned a discovery document we cannot read.`);
59
+ }
60
+ if (body.protocol > PUSH_PROTOCOL) {
61
+ throw new PublishError(`${body.name} speaks push protocol ${body.protocol}; this open-doc understands ${PUSH_PROTOCOL}. Upgrade costaff-workspace.`);
62
+ }
63
+ return body;
64
+ }
65
+ export async function startDeviceAuth(endpoint, discovery) {
66
+ const res = await fetch(resolveAgainst(endpoint, discovery.deviceAuthStart), {
67
+ method: 'POST',
68
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
69
+ body: JSON.stringify({ protocol: PUSH_PROTOCOL }),
70
+ });
71
+ const body = await readJson(res);
72
+ if (!res.ok) {
73
+ throw new PublishError(errorMessage(body, `Login could not start (${res.status}).`), res.status);
74
+ }
75
+ return body;
76
+ }
77
+ export async function pollDeviceAuth(endpoint, discovery, deviceCode) {
78
+ const res = await fetch(resolveAgainst(endpoint, discovery.deviceAuthPoll), {
79
+ method: 'POST',
80
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
81
+ body: JSON.stringify({ deviceCode }),
82
+ });
83
+ const body = await readJson(res);
84
+ if (res.status === 428)
85
+ return { status: 'pending' };
86
+ if (!res.ok) {
87
+ throw new PublishError(errorMessage(body, `Login failed (${res.status}).`), res.status);
88
+ }
89
+ return body;
90
+ }
91
+ export async function upload(endpoint, discovery, token, bytes) {
92
+ if (bytes.byteLength > discovery.maxBytes) {
93
+ throw new PublishError(`Bundle is ${formatBytes(bytes.byteLength)}; ${discovery.name} accepts at most ${formatBytes(discovery.maxBytes)}.`);
94
+ }
95
+ const res = await fetch(resolveAgainst(endpoint, discovery.push), {
96
+ method: 'POST',
97
+ headers: {
98
+ authorization: `Bearer ${token}`,
99
+ 'content-type': 'application/zip',
100
+ accept: 'application/json',
101
+ },
102
+ body: bytes,
103
+ });
104
+ const body = await readJson(res);
105
+ if (res.status === 401 || res.status === 403) {
106
+ throw new PublishError(errorMessage(body, 'Login expired — run with --login.'), res.status);
107
+ }
108
+ if (!res.ok) {
109
+ throw new PublishError(errorMessage(body, `Publish failed (${res.status}).`), res.status, errorCode(body));
110
+ }
111
+ if (!isPushResult(body)) {
112
+ throw new PublishError(`${discovery.name} accepted the upload but returned no share URL.`);
113
+ }
114
+ return body;
115
+ }
116
+ export function formatBytes(n) {
117
+ if (n < 1024)
118
+ return `${n} B`;
119
+ if (n < 1024 * 1024)
120
+ return `${(n / 1024).toFixed(1)} kB`;
121
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
122
+ }
123
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ export function credentialsPath(env = process.env) {
5
+ const base = env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), '.config');
6
+ return path.join(base, 'costaff-workspace', 'credentials.json');
7
+ }
8
+ /**
9
+ * Tokens are filed per endpoint origin, not per full URL: a receiver that moves
10
+ * its publish path must not orphan a valid login, and two endpoints on one host
11
+ * would share a session anyway.
12
+ */
13
+ export function credentialKey(endpoint) {
14
+ return new URL(endpoint).origin;
15
+ }
16
+ async function readStore(file) {
17
+ try {
18
+ const parsed = JSON.parse(await fs.readFile(file, 'utf8'));
19
+ if (typeof parsed === 'object' &&
20
+ parsed !== null &&
21
+ typeof parsed.endpoints === 'object') {
22
+ return parsed;
23
+ }
24
+ }
25
+ catch {
26
+ // A missing or corrupt store is a logged-out state, not a failure.
27
+ }
28
+ return { version: 1, endpoints: {} };
29
+ }
30
+ export function isExpired(cred, now = Date.now()) {
31
+ if (cred.expiresAt === undefined)
32
+ return false;
33
+ const at = Date.parse(cred.expiresAt);
34
+ return Number.isFinite(at) && at <= now;
35
+ }
36
+ export async function loadCredential(endpoint, file = credentialsPath()) {
37
+ const store = await readStore(file);
38
+ const cred = store.endpoints[credentialKey(endpoint)];
39
+ if (cred === undefined || isExpired(cred))
40
+ return null;
41
+ return cred;
42
+ }
43
+ export async function saveCredential(endpoint, cred, file = credentialsPath()) {
44
+ const store = await readStore(file);
45
+ store.endpoints[credentialKey(endpoint)] = cred;
46
+ await fs.mkdir(path.dirname(file), { recursive: true });
47
+ await fs.writeFile(file, `${JSON.stringify(store, null, 2)}\n`, { mode: 0o600 });
48
+ await fs.chmod(file, 0o600).catch(() => { });
49
+ }
50
+ export async function clearCredential(endpoint, file = credentialsPath()) {
51
+ const store = await readStore(file);
52
+ delete store.endpoints[credentialKey(endpoint)];
53
+ await fs.mkdir(path.dirname(file), { recursive: true });
54
+ await fs.writeFile(file, `${JSON.stringify(store, null, 2)}\n`, { mode: 0o600 });
55
+ }
56
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}