cloudtifact 0.1.1 → 0.1.3
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/dist/index.js +170 -22
- package/package.json +11 -12
package/dist/index.js
CHANGED
|
@@ -5618,6 +5618,12 @@ function datetime(params) {
|
|
|
5618
5618
|
function number(params) {
|
|
5619
5619
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
5620
5620
|
}
|
|
5621
|
+
//#endregion
|
|
5622
|
+
//#region ../contract/src/comment-images.ts
|
|
5623
|
+
/** A guest uploading an image sends their guest key under this header (the body is the image). */
|
|
5624
|
+
const GUEST_KEY_HEADER = "x-cloudtifact-guest-key";
|
|
5625
|
+
/** 128 random bits, hex. */
|
|
5626
|
+
const commentImageIdSchema = string().regex(/^[0-9a-f]{32}$/);
|
|
5621
5627
|
new TextEncoder();
|
|
5622
5628
|
//#endregion
|
|
5623
5629
|
//#region ../contract/src/schemas.ts
|
|
@@ -5697,12 +5703,18 @@ const errorSchema = object({ error: object({
|
|
|
5697
5703
|
object({
|
|
5698
5704
|
handle: handleSchema,
|
|
5699
5705
|
name: string().max(100).optional(),
|
|
5706
|
+
email: email().max(200).optional(),
|
|
5700
5707
|
label: _enum(["cli", "web"]).default("cli")
|
|
5701
5708
|
});
|
|
5702
5709
|
const devLoginOutput = object({
|
|
5703
5710
|
token: string(),
|
|
5704
5711
|
user: userSchema
|
|
5705
5712
|
});
|
|
5713
|
+
/** A one-hour token for the comment panel on published pages: reads a space and reads or posts its comments. */
|
|
5714
|
+
const embedTokenOutput = object({
|
|
5715
|
+
token: string(),
|
|
5716
|
+
expiresAt: number$1()
|
|
5717
|
+
});
|
|
5706
5718
|
object({ label: string().max(60).optional() });
|
|
5707
5719
|
const cliLoginStartOutput = object({
|
|
5708
5720
|
deviceCode: string(),
|
|
@@ -5749,6 +5761,25 @@ const listTokensOutput = object({ tokens: array(object({
|
|
|
5749
5761
|
const preferencesSchema = object({ developerMode: boolean() });
|
|
5750
5762
|
preferencesSchema.partial();
|
|
5751
5763
|
object({ handle: handleSchema });
|
|
5764
|
+
const emailCategoryIdSchema = _enum([
|
|
5765
|
+
"invites",
|
|
5766
|
+
"activity",
|
|
5767
|
+
"product"
|
|
5768
|
+
]);
|
|
5769
|
+
const emailPreferencesSchema = object({
|
|
5770
|
+
email: string().nullable(),
|
|
5771
|
+
categories: array(object({
|
|
5772
|
+
id: emailCategoryIdSchema,
|
|
5773
|
+
label: string(),
|
|
5774
|
+
description: string(),
|
|
5775
|
+
enabled: boolean()
|
|
5776
|
+
}))
|
|
5777
|
+
});
|
|
5778
|
+
object({
|
|
5779
|
+
invites: boolean(),
|
|
5780
|
+
activity: boolean(),
|
|
5781
|
+
product: boolean()
|
|
5782
|
+
}).partial().strict();
|
|
5752
5783
|
const meOutput = object({
|
|
5753
5784
|
user: userSchema,
|
|
5754
5785
|
workspaces: array(myWorkspaceSchema),
|
|
@@ -5815,7 +5846,9 @@ const workspaceInvitePreviewOutput = object({
|
|
|
5815
5846
|
invitedBy: userSchema.nullable(),
|
|
5816
5847
|
memberCount: number$1().int(),
|
|
5817
5848
|
expiresAt: number$1(),
|
|
5818
|
-
alreadyMember: boolean()
|
|
5849
|
+
alreadyMember: boolean(),
|
|
5850
|
+
email: string().nullable().default(null),
|
|
5851
|
+
wrongAccount: boolean().default(false)
|
|
5819
5852
|
});
|
|
5820
5853
|
const acceptWorkspaceInviteOutput = object({ workspace: myWorkspaceSchema });
|
|
5821
5854
|
const auditActionSchema = _enum([
|
|
@@ -5831,6 +5864,8 @@ const auditActionSchema = _enum([
|
|
|
5831
5864
|
"billing.checkout",
|
|
5832
5865
|
"space.created",
|
|
5833
5866
|
"space.updated",
|
|
5867
|
+
"space.moved",
|
|
5868
|
+
"space.deleted",
|
|
5834
5869
|
"room.link_changed",
|
|
5835
5870
|
"room.person_added",
|
|
5836
5871
|
"room.person_changed",
|
|
@@ -5909,6 +5944,11 @@ object({
|
|
|
5909
5944
|
viewers: viewersSchema.partial().optional(),
|
|
5910
5945
|
password: string().min(4).max(200).nullable().optional()
|
|
5911
5946
|
});
|
|
5947
|
+
object({ workspace: workspaceSlugSchema });
|
|
5948
|
+
const moveSpaceOutput = object({
|
|
5949
|
+
space: spaceSchema,
|
|
5950
|
+
moved: array(spaceIdSchema)
|
|
5951
|
+
});
|
|
5912
5952
|
object({ password: string().min(1).max(200) });
|
|
5913
5953
|
const unlockSpaceOutput = object({
|
|
5914
5954
|
token: string(),
|
|
@@ -6089,6 +6129,12 @@ const guestIdentitySchema = object({
|
|
|
6089
6129
|
name: string().trim().min(1).max(40),
|
|
6090
6130
|
key: guestKeySchema
|
|
6091
6131
|
});
|
|
6132
|
+
const commentImageSchema = object({
|
|
6133
|
+
id: string(),
|
|
6134
|
+
url: string(),
|
|
6135
|
+
contentType: string(),
|
|
6136
|
+
size: number$1().int()
|
|
6137
|
+
});
|
|
6092
6138
|
const commentSchema = object({
|
|
6093
6139
|
id: string(),
|
|
6094
6140
|
path: string(),
|
|
@@ -6098,6 +6144,7 @@ const commentSchema = object({
|
|
|
6098
6144
|
guestName: string().nullable(),
|
|
6099
6145
|
origin: commentOriginSchema.nullable(),
|
|
6100
6146
|
body: string(),
|
|
6147
|
+
images: array(commentImageSchema).default([]),
|
|
6101
6148
|
quote: string().nullable(),
|
|
6102
6149
|
anchor: anchorSchema.nullable(),
|
|
6103
6150
|
via: string().nullable(),
|
|
@@ -6120,6 +6167,30 @@ object({
|
|
|
6120
6167
|
]).default("open")
|
|
6121
6168
|
});
|
|
6122
6169
|
const listCommentsOutput = object({ threads: array(threadSchema) });
|
|
6170
|
+
object({
|
|
6171
|
+
before: number().int().positive().optional(),
|
|
6172
|
+
limit: number().int().min(1).max(50).default(20)
|
|
6173
|
+
});
|
|
6174
|
+
const waitingOutput = object({
|
|
6175
|
+
threads: array(object({
|
|
6176
|
+
thread: threadSchema,
|
|
6177
|
+
space: object({
|
|
6178
|
+
id: string(),
|
|
6179
|
+
name: string(),
|
|
6180
|
+
path: string(),
|
|
6181
|
+
kind: _enum([
|
|
6182
|
+
"repo",
|
|
6183
|
+
"room",
|
|
6184
|
+
"app"
|
|
6185
|
+
])
|
|
6186
|
+
}),
|
|
6187
|
+
reason: _enum(["participant", "owner"]),
|
|
6188
|
+
lastAt: number$1()
|
|
6189
|
+
})),
|
|
6190
|
+
total: number$1().int(),
|
|
6191
|
+
spaces: number$1().int(),
|
|
6192
|
+
next: number$1().nullable()
|
|
6193
|
+
});
|
|
6123
6194
|
object({
|
|
6124
6195
|
body: string().min(1).max(1e4),
|
|
6125
6196
|
guest: object({ key: guestKeySchema }).optional()
|
|
@@ -6139,6 +6210,7 @@ object({
|
|
|
6139
6210
|
anchor: anchorSchema.optional(),
|
|
6140
6211
|
parentId: string().optional(),
|
|
6141
6212
|
versionNumber: number$1().int().optional(),
|
|
6213
|
+
images: array(commentImageIdSchema).max(4).optional(),
|
|
6142
6214
|
guest: guestIdentitySchema.optional()
|
|
6143
6215
|
});
|
|
6144
6216
|
const DB_MAX_SQL_BYTES = 102400;
|
|
@@ -6451,7 +6523,9 @@ const ownerLinkOutput = object({
|
|
|
6451
6523
|
const planIdSchema = _enum([
|
|
6452
6524
|
"free",
|
|
6453
6525
|
"pro",
|
|
6526
|
+
"pro_annual",
|
|
6454
6527
|
"team",
|
|
6528
|
+
"team20",
|
|
6455
6529
|
"enterprise"
|
|
6456
6530
|
]);
|
|
6457
6531
|
const usageShape = {
|
|
@@ -6484,7 +6558,9 @@ const billingOutput = object({
|
|
|
6484
6558
|
object({ plan: _enum([
|
|
6485
6559
|
"free",
|
|
6486
6560
|
"pro",
|
|
6487
|
-
"
|
|
6561
|
+
"pro_annual",
|
|
6562
|
+
"team",
|
|
6563
|
+
"team20"
|
|
6488
6564
|
]) });
|
|
6489
6565
|
const checkoutOutput = object({ url: string() });
|
|
6490
6566
|
const portalOutput = object({ url: string() });
|
|
@@ -6538,8 +6614,11 @@ function createClient({ baseUrl, token, fetch: fetchImpl = fetch, agent, source,
|
|
|
6538
6614
|
const app = (id) => `/v1/apps/${enc$1(id)}`;
|
|
6539
6615
|
return {
|
|
6540
6616
|
devLogin: (input) => call("POST", "/v1/auth/dev-login", devLoginOutput, input),
|
|
6617
|
+
createEmbedToken: () => call("POST", "/v1/auth/embed-token", embedTokenOutput),
|
|
6541
6618
|
me: () => call("GET", "/v1/me", meOutput),
|
|
6542
6619
|
updatePreferences: (input) => call("PATCH", "/v1/me/preferences", preferencesSchema, input),
|
|
6620
|
+
emailPreferences: () => call("GET", "/v1/me/email-preferences", emailPreferencesSchema),
|
|
6621
|
+
updateEmailPreferences: (input) => call("PATCH", "/v1/me/email-preferences", emailPreferencesSchema, input),
|
|
6543
6622
|
/** Changes your handle; your personal workspace's slug follows when it matched the old handle. */
|
|
6544
6623
|
updateHandle: (input) => call("PATCH", "/v1/me/handle", userSchema, input),
|
|
6545
6624
|
/** Deletes your account for good: tokens stop working, workspaces only you are in are deleted. */
|
|
@@ -6566,6 +6645,8 @@ function createClient({ baseUrl, token, fetch: fetchImpl = fetch, agent, source,
|
|
|
6566
6645
|
createSpace: (input) => call("POST", "/v1/spaces", spaceSchema, input),
|
|
6567
6646
|
getSpace: (id) => call("GET", space(id), spaceSchema),
|
|
6568
6647
|
updateSpace: (id, input) => call("PATCH", space(id), spaceSchema, input),
|
|
6648
|
+
deleteSpace: (id) => call("DELETE", space(id), null),
|
|
6649
|
+
moveSpace: (id, input) => call("POST", `${space(id)}/move`, moveSpaceOutput, input),
|
|
6569
6650
|
head: (id) => call("GET", `${space(id)}/head`, headOutput),
|
|
6570
6651
|
unlockSpace: (id, input) => call("POST", `${space(id)}/unlock`, unlockSpaceOutput, input),
|
|
6571
6652
|
recordView: (id, input) => call("POST", `${space(id)}/views`, null, input),
|
|
@@ -6610,7 +6691,19 @@ function createClient({ baseUrl, token, fetch: fetchImpl = fetch, agent, source,
|
|
|
6610
6691
|
if (query.status) q.set("status", query.status);
|
|
6611
6692
|
return call("GET", `${space(id)}/comments${q.size ? `?${q}` : ""}`, listCommentsOutput);
|
|
6612
6693
|
},
|
|
6694
|
+
waiting: (slug, query = {}) => {
|
|
6695
|
+
const q = new URLSearchParams();
|
|
6696
|
+
if (query.before) q.set("before", String(query.before));
|
|
6697
|
+
if (query.limit) q.set("limit", String(query.limit));
|
|
6698
|
+
return call("GET", `/v1/workspaces/${enc$1(slug)}/waiting${q.size ? `?${q}` : ""}`, waitingOutput);
|
|
6699
|
+
},
|
|
6613
6700
|
createComment: (id, input) => call("POST", `${space(id)}/comments`, commentSchema, input),
|
|
6701
|
+
/** Uploads an image for a comment; post it with createComment's `images`. A guest passes their key. */
|
|
6702
|
+
uploadCommentImage: (id, body, opts) => call("POST", `${space(id)}/comment-images`, commentImageSchema, body, {
|
|
6703
|
+
"content-type": opts.contentType || "application/octet-stream",
|
|
6704
|
+
"content-length": String(opts.size),
|
|
6705
|
+
...opts.guestKey ? { [GUEST_KEY_HEADER]: opts.guestKey } : {}
|
|
6706
|
+
}),
|
|
6614
6707
|
editComment: (commentId, input) => call("PATCH", `/v1/comments/${enc$1(commentId)}`, commentSchema, input),
|
|
6615
6708
|
commentRevisions: (commentId) => call("GET", `/v1/comments/${enc$1(commentId)}/revisions`, commentRevisionsOutput),
|
|
6616
6709
|
deleteComment: (commentId, input = {}) => call("DELETE", `/v1/comments/${enc$1(commentId)}`, null, input),
|
|
@@ -6647,6 +6740,13 @@ function createClient({ baseUrl, token, fetch: fetchImpl = fetch, agent, source,
|
|
|
6647
6740
|
deleteMessage: (roomId, messageId) => send("DELETE", `${room(roomId)}/messages/${enc$1(messageId)}`),
|
|
6648
6741
|
listSources: (id) => call("GET", `${room(id)}/sources`, listSourcesOutput),
|
|
6649
6742
|
uploadSource: (id, name, bytes, contentType) => call("POST", `${room(id)}/sources?name=${enc$1(name)}`, sourceSchema, bytes, { "content-type": contentType }),
|
|
6743
|
+
/** A source file's bytes as an attachment (the raw Response, to stream or save). */
|
|
6744
|
+
downloadSource: (id, sourceId) => request("GET", `${room(id)}/sources/${enc$1(sourceId)}/raw`),
|
|
6745
|
+
/** Uploads an image for an image block in the room's doc; put its `url` in the block's src. */
|
|
6746
|
+
uploadRoomImage: (id, body, opts) => call("POST", `${room(id)}/images`, commentImageSchema, body, {
|
|
6747
|
+
"content-type": opts.contentType || "application/octet-stream",
|
|
6748
|
+
"content-length": String(opts.size)
|
|
6749
|
+
}),
|
|
6650
6750
|
updateSource: (id, sourceId, input) => call("PATCH", `${room(id)}/sources/${enc$1(sourceId)}`, sourceSchema, input),
|
|
6651
6751
|
deleteSource: (id, sourceId) => call("DELETE", `${room(id)}/sources/${enc$1(sourceId)}`, null),
|
|
6652
6752
|
listApps: (query = {}) => {
|
|
@@ -6725,8 +6825,8 @@ function urlsFor(domain, userContent = USER_CONTENT_DOMAIN) {
|
|
|
6725
6825
|
};
|
|
6726
6826
|
}
|
|
6727
6827
|
const PUBLIC_URLS = urlsFor(PUBLIC_DOMAIN);
|
|
6728
|
-
/** The one-line install for the CLI
|
|
6729
|
-
const installCommand = (
|
|
6828
|
+
/** The one-line install for the CLI. */
|
|
6829
|
+
const installCommand = () => "npm i -g cloudtifact";
|
|
6730
6830
|
//#endregion
|
|
6731
6831
|
//#region ../contract/src/guest-writes.ts
|
|
6732
6832
|
/**
|
|
@@ -10494,7 +10594,8 @@ function commentsData(threads, opts) {
|
|
|
10494
10594
|
edited: c.editedBy ? `${c.editedBy.handle}${c.editedVia ? ` via ${c.editedVia}` : ""}` : null,
|
|
10495
10595
|
status: t.resolvedAt === null ? "open" : "resolved",
|
|
10496
10596
|
quote: c.quote,
|
|
10497
|
-
body: c.body
|
|
10597
|
+
body: c.body,
|
|
10598
|
+
images: c.images?.length ? c.images.map((i) => i.url).join(" ") : null
|
|
10498
10599
|
})));
|
|
10499
10600
|
return {
|
|
10500
10601
|
space: opts.spaceUrl ?? null,
|
|
@@ -11117,6 +11218,19 @@ function errorText(e) {
|
|
|
11117
11218
|
//#endregion
|
|
11118
11219
|
//#region src/publish.ts
|
|
11119
11220
|
const ENTRY = "index.html";
|
|
11221
|
+
/** A public page's link with the comment panel already open (cloudtifact.app/<id>/?comments). */
|
|
11222
|
+
function commentsUrl(shareUrl) {
|
|
11223
|
+
return `${shareUrl}${shareUrl.includes("?") ? "&" : "?"}comments`;
|
|
11224
|
+
}
|
|
11225
|
+
/**
|
|
11226
|
+
* What `publish` prints after the version line. A public page gets two links to the same page: on its own (a micro
|
|
11227
|
+
* app, with a Comment button in the corner) and with the comment panel already open. A private page has only the
|
|
11228
|
+
* app link, which members open inside the Cloudtifact viewer.
|
|
11229
|
+
*/
|
|
11230
|
+
function linkLines(space) {
|
|
11231
|
+
if (space.shareUrl) return [`Page: ${space.shareUrl}`, `With comments: ${commentsUrl(space.shareUrl)}`];
|
|
11232
|
+
return [`Link: ${space.url}`, `Private: only members of the ${space.workspace.slug} workspace can open it. To make it public, change visibility in the space settings on the website.`];
|
|
11233
|
+
}
|
|
11120
11234
|
/** A local, relative reference, or null for absolute URLs, data:, anchors, etc. Drops ?query and #hash. */
|
|
11121
11235
|
function localRef(raw) {
|
|
11122
11236
|
const ref = raw.trim().replace(/^['"]|['"]$/g, "");
|
|
@@ -11426,6 +11540,8 @@ const from = (c) => {
|
|
|
11426
11540
|
};
|
|
11427
11541
|
const who = (c) => (c.guestName ? `${c.guestName} (guest)` : `@${c.author.handle}${c.via ? ` via ${agentLabel(c.via)}` : ""}`) + from(c);
|
|
11428
11542
|
const edited = (c) => c.editedBy ? ` (edited by @${c.editedBy.handle}${c.editedVia ? ` via ${agentLabel(c.editedVia)}` : ""}; \`cloudtifact history ${c.id}\`)` : "";
|
|
11543
|
+
/** A comment's images as Markdown image links (an agent can fetch them), then a blank line; nothing if none. */
|
|
11544
|
+
const images = (c) => c.images?.length ? [...c.images.map((img, i) => ``), ""] : [];
|
|
11429
11545
|
/** Open threads as Markdown an agent can act on directly. */
|
|
11430
11546
|
function threadsToMarkdown(threads, opts) {
|
|
11431
11547
|
const spaceFlag = opts.defaultSpace ? "" : ` --space ${opts.spaceId}`;
|
|
@@ -11442,8 +11558,8 @@ function threadsToMarkdown(threads, opts) {
|
|
|
11442
11558
|
out.push(`### Comment \`${t.id}\`${status}`, "");
|
|
11443
11559
|
out.push(`${who(t)} on v${t.versionNumber}, ${when(t.createdAt)}${edited(t)}:`, "");
|
|
11444
11560
|
if (t.quote) out.push("On this text:", "", quote(t.quote), "");
|
|
11445
|
-
out.push(t.body.trim(), "");
|
|
11446
|
-
for (const r of t.replies) out.push(`- Reply \`${r.id}\` from ${who(r)} (${when(r.createdAt)})${edited(r)}:`, "", r.body.trim().replace(/^/gm, " "), "");
|
|
11561
|
+
out.push(t.body.trim(), "", ...images(t));
|
|
11562
|
+
for (const r of t.replies) out.push(`- Reply \`${r.id}\` from ${who(r)} (${when(r.createdAt)})${edited(r)}:`, "", r.body.trim().replace(/^/gm, " "), "", ...images(r).map((l) => l ? ` ${l}` : l));
|
|
11447
11563
|
out.push("To answer and close it:", "", "```sh", `cloudtifact reply ${t.id}${spaceFlag} "<what you changed>"`, t.resolvedAt ? `cloudtifact unresolve ${t.id}` : `cloudtifact resolve ${t.id}`, "```", "");
|
|
11448
11564
|
}
|
|
11449
11565
|
}
|
|
@@ -11771,7 +11887,7 @@ description: Use when you make an HTML page for the user (publish it and share t
|
|
|
11771
11887
|
|
|
11772
11888
|
The folder named by \`dir\` in \`.cloudtifact.json\` (at the repo root) is published to Cloudtifact. Every push makes a new version; readers comment on the published pages.
|
|
11773
11889
|
|
|
11774
|
-
If the \`cloudtifact\` command isn't found, install it: \`${installCommand()}\` (
|
|
11890
|
+
If the \`cloudtifact\` command isn't found, install it: \`${installCommand()}\` (needs Node.js 20 or later). \`cloudtifact upgrade\` updates it to the latest version.
|
|
11775
11891
|
|
|
11776
11892
|
## Before editing docs in that folder
|
|
11777
11893
|
|
|
@@ -11794,12 +11910,16 @@ Published file types: Markdown, HTML, and the files pages load (CSS, JS, JSON, i
|
|
|
11794
11910
|
|
|
11795
11911
|
## Publishing HTML pages
|
|
11796
11912
|
|
|
11797
|
-
When you make an HTML page for the user, run \`cloudtifact publish <file.html>\` and give them the
|
|
11913
|
+
When you make an HTML page for the user, run \`cloudtifact publish <file.html>\` and give them the links it prints. A public page prints two links to the same page: \`Page:\` opens it on its own (with a Comment button in the corner), and \`With comments:\` opens it with the comment panel already open. Give both, or the one the user asked for. A private page prints one \`Link:\`, for members of the workspace. It uploads the page plus the local files it references (CSS, JS, images).
|
|
11798
11914
|
|
|
11799
11915
|
- To update it, edit the same file and publish it again: new version, same link.
|
|
11800
11916
|
- Keep supporting files next to the HTML (or in subfolders) and reference them with relative paths (\`style.css\`, \`img/logo.png\`). Absolute paths and files outside the page's folder aren't included.
|
|
11801
11917
|
- Pages are private by default; add \`--public\` on the first publish for a link anyone can open. \`cloudtifact artifacts\` lists what you've published.
|
|
11802
11918
|
|
|
11919
|
+
## Space files
|
|
11920
|
+
|
|
11921
|
+
People can add files to a space for the work (chat exports, emails, PDFs, spreadsheets, screenshots and other images). \`cloudtifact sources --space <id> --toon\` lists them; \`cloudtifact download --source "<name or id>" --space <id>\` saves one locally so you can read it. Open downloaded images to look at them.
|
|
11922
|
+
|
|
11803
11923
|
## Workspaces
|
|
11804
11924
|
|
|
11805
11925
|
Spaces live in workspaces (personal or team; the user can belong to several). \`cloudtifact publish\` and \`cloudtifact init\` create new spaces in the current workspace and print which one; pass \`--workspace <slug>\` to pick another. \`cloudtifact workspace list\` shows the user's workspaces (current one marked); \`cloudtifact workspace use <slug>\` switches the default. Pushing to an existing space doesn't need a workspace. Only change the default when the user asks.
|
|
@@ -11969,6 +12089,13 @@ function findUpload(uploads, ref) {
|
|
|
11969
12089
|
const names = uploads.slice(0, 10).map((u) => ` ${u.name} (${u.id})`);
|
|
11970
12090
|
throw new CliError(uploads.length ? `No upload named or with id "${ref}". Uploads in this space:\n${names.join("\n")}` : "This space has no uploads.");
|
|
11971
12091
|
}
|
|
12092
|
+
/** A file in a space's "Files the AI reads" by id, else the newest one with that exact name. */
|
|
12093
|
+
function findSource(sources, ref) {
|
|
12094
|
+
const hit = sources.find((s) => s.id === ref) ?? sources.find((s) => s.name === ref);
|
|
12095
|
+
if (hit) return hit;
|
|
12096
|
+
const names = sources.slice(0, 10).map((s) => ` ${s.name} (${s.id})`);
|
|
12097
|
+
throw new CliError(sources.length ? `No file named or with id "${ref}" in this space. Its files:\n${names.join("\n")}` : "This space has no files for the AI.");
|
|
12098
|
+
}
|
|
11972
12099
|
/**
|
|
11973
12100
|
* Streams a download to `dest` (via a temp file, renamed at the end), checking the SHA-256 the API sent.
|
|
11974
12101
|
* Refuses to overwrite unless `force`.
|
|
@@ -12076,7 +12203,7 @@ function workspaceTextLines(workspaces, currentSlug) {
|
|
|
12076
12203
|
}
|
|
12077
12204
|
//#endregion
|
|
12078
12205
|
//#region src/program.ts
|
|
12079
|
-
const VERSION = "0.1.
|
|
12206
|
+
const VERSION = "0.1.3";
|
|
12080
12207
|
const program = new Command().name("cloudtifact").description("Publish pages and docs to Cloudtifact as links people comment on, with every version kept and the comments handed back to your agent.").version(VERSION);
|
|
12081
12208
|
program.command("login").description("Sign in through your browser (GitHub, Google or email) and save a token to the global config").option("--no-browser", "don't open a browser, just print the URL (automatic over SSH)").action(run(async (opts) => {
|
|
12082
12209
|
const config = await readGlobalConfig();
|
|
@@ -12460,13 +12587,6 @@ for (const [name, verb] of [["resolve", "Resolved"], ["unresolve", "Reopened"]])
|
|
|
12460
12587
|
const c = await (name === "resolve" ? client.resolveComment(commentId) : client.unresolveComment(commentId));
|
|
12461
12588
|
console.log(`${verb} ${c.id} on ${c.path}.`);
|
|
12462
12589
|
}));
|
|
12463
|
-
function printLink(space) {
|
|
12464
|
-
if (space.shareUrl) console.log(space.shareUrl);
|
|
12465
|
-
else {
|
|
12466
|
-
console.log(space.url);
|
|
12467
|
-
console.log(`Private: only members of the ${space.workspace.slug} workspace can open it. To make it public, change visibility in the space settings on the website.`);
|
|
12468
|
-
}
|
|
12469
|
-
}
|
|
12470
12590
|
program.command("publish").description("Publish an HTML page (with the files it references) or a folder; prints its link").argument("<path>", "an .html file or a folder with index.html").option("--public", "anyone with the link can view (only when first published; default: private)").option("--name <name>", "space name (default: the page's <title>, else the file name)").option("--workspace <slug>", "workspace for a new space (default: the current one, see `workspace list`)").option("--entry <file>", "folder only: the page to publish as index.html").option("-m, --message <message>", "version message").option("--no-db", "don't run the db/*.sql files next to the page").action(run(async (target, opts) => {
|
|
12471
12591
|
const abs = path.resolve(target);
|
|
12472
12592
|
if (!existsSync(abs)) throw new CliError(`${target} does not exist.`);
|
|
@@ -12512,7 +12632,7 @@ program.command("publish").description("Publish an HTML page (with the files it
|
|
|
12512
12632
|
const n = bundle.files.length;
|
|
12513
12633
|
if (r.status === "pushed") console.log(`Published "${space.name}" v${r.version} (${n} file${n === 1 ? "" : "s"}; ${summarize(r.diff)})`);
|
|
12514
12634
|
else console.log(`No changes since v${space.latestVersion}: "${space.name}"`);
|
|
12515
|
-
|
|
12635
|
+
for (const line of linkLines(space)) console.log(line);
|
|
12516
12636
|
}));
|
|
12517
12637
|
program.command("artifacts").description("List pages published with `cloudtifact publish`").option("--toon", "print TOON (compact, for agents)").option("--json", "print JSON").action(run(async (opts) => {
|
|
12518
12638
|
const map = await readArtifacts();
|
|
@@ -12528,7 +12648,8 @@ program.command("artifacts").description("List pages published with `cloudtifact
|
|
|
12528
12648
|
workspace: space?.workspace.slug ?? null,
|
|
12529
12649
|
visibility: space?.visibility ?? null,
|
|
12530
12650
|
latestVersion: space?.latestVersion ?? null,
|
|
12531
|
-
link: space ? space.shareUrl ?? space.url : null
|
|
12651
|
+
link: space ? space.shareUrl ?? space.url : null,
|
|
12652
|
+
commentsLink: space ? space.shareUrl ? commentsUrl(space.shareUrl) : space.url : null
|
|
12532
12653
|
});
|
|
12533
12654
|
}
|
|
12534
12655
|
const format = resolveFormat(opts);
|
|
@@ -12577,13 +12698,40 @@ program.command("uploads").description("List the files uploaded to a space, newe
|
|
|
12577
12698
|
if (!rows.length) return console.log("No uploads yet. Add some with `cloudtifact upload <files...>`.");
|
|
12578
12699
|
for (const r of rows) console.log(`${r.id} ${r.at} UTC ${(r.size / 1024 / 1024).toFixed(1).padStart(6)} MB ${r.name}`);
|
|
12579
12700
|
}));
|
|
12580
|
-
program.command("
|
|
12581
|
-
|
|
12701
|
+
program.command("sources").description("List a space's \"Files the AI reads\" (chats, emails, PDFs, images...); get one with download --source").option("--space <id>", "space id or link (default: the first target in .cloudtifact.json)").option("--toon", "print TOON (compact, for agents)").option("--json", "print JSON").action(run(async (opts) => {
|
|
12702
|
+
const { spaceId } = await pickSpace(opts.space, { noRepo: "Pass --space <id>, or run inside a linked repo." });
|
|
12703
|
+
const { client } = await getClient({ auth: false });
|
|
12704
|
+
const { sources } = await client.listSources(spaceId);
|
|
12705
|
+
const rows = sources.map((s) => ({
|
|
12706
|
+
id: s.id,
|
|
12707
|
+
name: s.name,
|
|
12708
|
+
type: s.contentType,
|
|
12709
|
+
size: s.size,
|
|
12710
|
+
status: s.status,
|
|
12711
|
+
summary: s.summary,
|
|
12712
|
+
by: s.uploadedBy?.handle ?? null,
|
|
12713
|
+
at: new Date(s.createdAt).toISOString().slice(0, 16).replace("T", " ")
|
|
12714
|
+
}));
|
|
12715
|
+
const format = resolveFormat(opts);
|
|
12716
|
+
if (format !== "text") return printData(format, {
|
|
12717
|
+
spaceId,
|
|
12718
|
+
sources: rows
|
|
12719
|
+
});
|
|
12720
|
+
if (!rows.length) return console.log("No files yet. People add them in the space's Files panel.");
|
|
12721
|
+
for (const r of rows) console.log(`${r.id} ${r.at} UTC ${(r.size / 1024 / 1024).toFixed(1).padStart(6)} MB ${r.name} (${r.summary ?? r.status})`);
|
|
12722
|
+
console.log("\nDownload one with `cloudtifact download --source \"<name or id>\"`.");
|
|
12723
|
+
}));
|
|
12724
|
+
program.command("download").description("Download an uploaded file (by name or id), a pushed file with --file, or a space file with --source").argument("[name]", "the upload's name or id").option("--file <path>", "download a pushed file instead (path in the space)").option("--source <name>", "download one of the space's \"Files the AI reads\" instead (name or id; see `sources`)").option("--version <n>", "with --file: the version to download from (default: latest)").option("-o, --output <path>", "where to save it (default: the file's name, in the current folder)").option("--force", "overwrite an existing file").option("--space <id>", "space id or link (default: the first target in .cloudtifact.json)").action(run(async (name, opts) => {
|
|
12725
|
+
if (!name && !opts.file && !opts.source) throw new CliError("Name an upload (see `cloudtifact uploads`), or pass --file <path> or --source <name>.");
|
|
12582
12726
|
const { spaceId } = await pickSpace(opts.space, { noRepo: "Pass --space <id>, or run inside a linked repo." });
|
|
12583
12727
|
const { client } = await getClient({ auth: false });
|
|
12584
12728
|
let res;
|
|
12585
12729
|
let fallback;
|
|
12586
|
-
if (opts.
|
|
12730
|
+
if (opts.source) {
|
|
12731
|
+
const source = findSource((await client.listSources(spaceId)).sources, opts.source);
|
|
12732
|
+
res = await client.downloadSource(spaceId, source.id);
|
|
12733
|
+
fallback = source.name;
|
|
12734
|
+
} else if (opts.file) {
|
|
12587
12735
|
const v = opts.version ? Number(opts.version) : void 0;
|
|
12588
12736
|
if (v !== void 0 && !(Number.isInteger(v) && v > 0)) throw new CliError("--version must be a positive integer.");
|
|
12589
12737
|
res = await client.downloadFile(spaceId, opts.file, v);
|
package/package.json
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudtifact",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Publish pages and docs to Cloudtifact as links people comment on, with every version kept and the comments handed back to your agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "The Cloudtifact authors",
|
|
7
7
|
"homepage": "https://cloudtifact.com/docs/reference/cli",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/cloudtifact/cloudtifact.git",
|
|
11
|
-
"directory": "packages/cli"
|
|
12
|
-
},
|
|
13
8
|
"bugs": {
|
|
14
|
-
"url": "https://
|
|
9
|
+
"url": "https://cloudtifact.com/support"
|
|
15
10
|
},
|
|
16
11
|
"keywords": [
|
|
17
12
|
"cloudtifact",
|
|
@@ -37,15 +32,15 @@
|
|
|
37
32
|
"node": ">=20"
|
|
38
33
|
},
|
|
39
34
|
"devDependencies": {
|
|
35
|
+
"@cloudtifact/contract": "0.1.0",
|
|
36
|
+
"@cloudtifact/typescript-config": "0.1.0",
|
|
37
|
+
"@toon-format/toon": "^4.1.1",
|
|
40
38
|
"@types/node": "^20.19.0",
|
|
41
39
|
"commander": "^15.0.0",
|
|
42
40
|
"tsdown": "^0.23.0",
|
|
43
41
|
"typescript": "^5.9.3",
|
|
44
42
|
"vitest": "~4.1.11",
|
|
45
|
-
"zod": "^4.6.5"
|
|
46
|
-
"@toon-format/toon": "^4.1.1",
|
|
47
|
-
"@cloudtifact/contract": "0.1.0",
|
|
48
|
-
"@cloudtifact/typescript-config": "0.1.0"
|
|
43
|
+
"zod": "^4.6.5"
|
|
49
44
|
},
|
|
50
45
|
"publishConfig": {
|
|
51
46
|
"access": "public"
|
|
@@ -55,5 +50,9 @@
|
|
|
55
50
|
"dev": "tsdown --watch",
|
|
56
51
|
"typecheck": "tsc --noEmit",
|
|
57
52
|
"test": "vitest run"
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/Cloudtifact/cloudtifact-cli.git"
|
|
58
57
|
}
|
|
59
|
-
}
|
|
58
|
+
}
|