@helpfeel/cosense-cli 1.13.0 → 1.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helpfeel/cosense-cli",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Cosense (旧Scrapbox) のページを読み・調べ・編集するAgent Skill用のCLI",
5
5
  "homepage": "https://github.com/helpfeel/cosense-cli",
6
6
  "license": "MIT",
package/src/cli.ts CHANGED
@@ -42,6 +42,11 @@ import {
42
42
  listPagesHelp,
43
43
  listPagesSummary
44
44
  } from './commands/listPages.ts';
45
+ import {
46
+ listPageSnapshots,
47
+ listPageSnapshotsHelp,
48
+ listPageSnapshotsSummary
49
+ } from './commands/listPageSnapshots.ts';
45
50
  import {
46
51
  listProjects,
47
52
  listProjectsHelp,
@@ -68,6 +73,11 @@ import {
68
73
  readPageHelp,
69
74
  readPageSummary
70
75
  } from './commands/readPage.ts';
76
+ import {
77
+ readPageSnapshot,
78
+ readPageSnapshotHelp,
79
+ readPageSnapshotSummary
80
+ } from './commands/readPageSnapshot.ts';
71
81
  import {
72
82
  readProjectMembers,
73
83
  readProjectMembersHelp,
@@ -149,6 +159,11 @@ const commands: Record<string, CommandSpec> = {
149
159
  help: browseRelatedPagesHelp
150
160
  },
151
161
  readPage: { handler: readPage, summary: readPageSummary, help: readPageHelp },
162
+ readPageSnapshot: {
163
+ handler: readPageSnapshot,
164
+ summary: readPageSnapshotSummary,
165
+ help: readPageSnapshotHelp
166
+ },
152
167
  readFileInfo: {
153
168
  handler: readFileInfo,
154
169
  summary: readFileInfoSummary,
@@ -179,6 +194,11 @@ const commands: Record<string, CommandSpec> = {
179
194
  summary: listPagesSummary,
180
195
  help: listPagesHelp
181
196
  },
197
+ listPageSnapshots: {
198
+ handler: listPageSnapshots,
199
+ summary: listPageSnapshotsSummary,
200
+ help: listPageSnapshotsHelp
201
+ },
182
202
  list1hopLinks: {
183
203
  handler: list1hopLinks,
184
204
  summary: list1hopLinksSummary,
@@ -0,0 +1,65 @@
1
+ import { enrichTimestampsOf } from '../lib/enrichTimestamps.ts';
2
+ import { parseProjectUrlStrict } from '../lib/parseUrl.ts';
3
+ import { requestJson } from '../lib/request.ts';
4
+ import { resolveCredential } from '../lib/settings.ts';
5
+
6
+ export const listPageSnapshotsSummary =
7
+ 'ページのsnapshot一覧(Page History)をpageId起点で取得する';
8
+
9
+ export const listPageSnapshotsHelp = `listPageSnapshots - ページのsnapshot一覧(Page History)をpageId起点で取得する
10
+
11
+ Usage:
12
+ cosense listPageSnapshots <projectUrl> <pageId>
13
+
14
+ 引数:
15
+ <projectUrl> プロジェクトのURL (例: https://scrapbox.io/shokai)。 末尾に余分なpathがあるとerror
16
+ <pageId> ページの不変ID。browsePage / readPage の出力に含まれる
17
+
18
+ 戻り値(top-levelのkey):
19
+ pageId string 対象ページのID
20
+ timestamps Array<{ id, created }> snapshotの一覧。新しい順、最新100件まで
21
+ id string snapshotのID。readPageSnapshot に渡す
22
+ created string snapshot時点のページ最終更新時刻
23
+
24
+ 戻り値のJSON抜粋例:
25
+ {
26
+ "pageId": "5803c5397ad353b0aee24341",
27
+ "timestamps": [
28
+ {
29
+ "id": "5cef4052dd15ed00447c1405",
30
+ "created": "2019-05-30T11:29+09:00 (7 years ago)"
31
+ }
32
+ ]
33
+ }
34
+
35
+ HTTPエラー:
36
+ HTTP 401 認証なし
37
+ HTTP 403 権限不足(projectのmemberでない 等)
38
+ HTTP 404 pageId に対応するpageが存在しない / pageId が不正な形式
39
+ `;
40
+
41
+ interface ListPageSnapshotsData {
42
+ timestamps?: { id?: string; created?: number | string }[];
43
+ }
44
+
45
+ export const listPageSnapshots = async (args: string[]): Promise<void> => {
46
+ if (args.length !== 2) {
47
+ throw new Error('Usage: cosense listPageSnapshots <projectUrl> <pageId>');
48
+ }
49
+ const [projectUrl, pageId] = args as [string, string];
50
+
51
+ const { origin, projectName } = parseProjectUrlStrict(projectUrl);
52
+ const credential = resolveCredential(origin, projectName);
53
+
54
+ // APIはx-following-idレスポンスヘッダで100件ずつページングするが、最新100件のみ返し追跡しない
55
+ const apiUrl = `${origin}/api/page-snapshots/${projectName}/${pageId}`;
56
+ const data = (await requestJson(apiUrl, {
57
+ credential
58
+ })) as ListPageSnapshotsData;
59
+
60
+ for (const timestamp of data.timestamps ?? []) {
61
+ enrichTimestampsOf(timestamp as Record<string, unknown>, ['created']);
62
+ }
63
+
64
+ process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
65
+ };
@@ -0,0 +1,138 @@
1
+ import { enrichTimestampsOf } from '../lib/enrichTimestamps.ts';
2
+ import { parseProjectUrlStrict } from '../lib/parseUrl.ts';
3
+ import { requestJson } from '../lib/request.ts';
4
+ import { enrichUser, fetchUserMap } from '../lib/resolveUsers.ts';
5
+ import { resolveCredential } from '../lib/settings.ts';
6
+
7
+ export const readPageSnapshotSummary =
8
+ 'snapshotのIDを指定してPage History上の過去の本文を読む';
9
+
10
+ export const readPageSnapshotHelp = `readPageSnapshot - snapshotのIDを指定してPage History上の過去の本文を読む
11
+
12
+ Usage:
13
+ cosense readPageSnapshot <projectUrl> <pageId> <snapshotId>
14
+
15
+ 引数:
16
+ <projectUrl> プロジェクトのURL (例: https://scrapbox.io/shokai)。 末尾に余分なpathがあるとerror
17
+ <pageId> ページの不変ID。browsePage / readPage の出力に含まれる
18
+ <snapshotId> snapshotのID。listPageSnapshots の timestamps[].id から取得する
19
+
20
+ 戻り値(top-levelのkey):
21
+ page object 現在のページのメタデータ。field構成は readPage のtop-levelから本文・リンク系
22
+ (lines / links / icons 等) を除いたもの。現在の本文は含まれない
23
+ snapshot object snapshot時点のページ
24
+ title string snapshot時点のページタイトル
25
+ created string snapshot時点のページ最終更新時刻
26
+ lines Array<Line> snapshot時点の本文。Line = { id, text, user, created, updated }
27
+
28
+ 戻り値のJSON抜粋例:
29
+ {
30
+ "page": {
31
+ "id": "5803c5397ad353b0aee24341",
32
+ "title": "page1",
33
+ "commitId": "5cef401cfca37d0018dd9ead"
34
+ },
35
+ "snapshot": {
36
+ "title": "page1",
37
+ "created": "2019-05-30T11:29+09:00 (7 years ago)",
38
+ "lines": [
39
+ {
40
+ "id": "5803c5397ad353b0aee24341",
41
+ "text": "page1",
42
+ "user": { "id": "5803c4bd7ad353b0aee24328", "name": "shokai" },
43
+ "created": "2019-05-30T11:29+09:00 (7 years ago)",
44
+ "updated": "2019-05-30T11:29+09:00 (7 years ago)"
45
+ }
46
+ ]
47
+ }
48
+ }
49
+
50
+ 絞り込み例(jqで欲しい部分だけ抜き出す):
51
+ snapshot時点の各行のテキストだけ:
52
+ cosense readPageSnapshot <projectUrl> <pageId> <snapshotId> | jq -r '.snapshot.lines[].text'
53
+
54
+ HTTPエラー:
55
+ HTTP 401 認証なし
56
+ HTTP 403 権限不足(projectのmemberでない 等)
57
+ HTTP 404 pageId に対応するpageが存在しない / snapshotId がこのページのsnapshotでない
58
+ HTTP 422 snapshotId が不正な形式
59
+ `;
60
+
61
+ interface UserRef {
62
+ id: string;
63
+ }
64
+
65
+ interface SnapshotLine {
66
+ id?: string;
67
+ text?: string;
68
+ userId?: string;
69
+ user?: UserRef;
70
+ created?: number | string;
71
+ updated?: number | string;
72
+ }
73
+
74
+ interface PageMetadata {
75
+ user?: UserRef | null;
76
+ lastUpdateUser?: UserRef | null;
77
+ users?: UserRef[];
78
+ }
79
+
80
+ interface ReadPageSnapshotData {
81
+ page?: PageMetadata;
82
+ snapshot?: {
83
+ created?: number | string;
84
+ lines?: SnapshotLine[];
85
+ };
86
+ }
87
+
88
+ export const readPageSnapshot = async (args: string[]): Promise<void> => {
89
+ if (args.length !== 3) {
90
+ throw new Error(
91
+ 'Usage: cosense readPageSnapshot <projectUrl> <pageId> <snapshotId>'
92
+ );
93
+ }
94
+ const [projectUrl, pageId, snapshotId] = args as [string, string, string];
95
+
96
+ const { origin, projectName } = parseProjectUrlStrict(projectUrl);
97
+ const credential = resolveCredential(origin, projectName);
98
+
99
+ const apiUrl = `${origin}/api/page-snapshots/${projectName}/${pageId}/${snapshotId}`;
100
+ const data = (await requestJson(apiUrl, {
101
+ credential
102
+ })) as ReadPageSnapshotData;
103
+
104
+ const userMap = await fetchUserMap(origin, projectName);
105
+ const page = data.page;
106
+ if (page) {
107
+ enrichUser(page.user, userMap);
108
+ enrichUser(page.lastUpdateUser, userMap);
109
+ for (const editor of page.users ?? []) {
110
+ enrichUser(editor, userMap);
111
+ }
112
+ enrichTimestampsOf(page as Record<string, unknown>, [
113
+ 'created',
114
+ 'updated',
115
+ 'accessed',
116
+ 'snapshotCreated',
117
+ 'lastAccessed'
118
+ ]);
119
+ }
120
+ const snapshot = data.snapshot;
121
+ if (snapshot) {
122
+ for (const line of snapshot.lines ?? []) {
123
+ const userId = line.userId;
124
+ if (typeof userId === 'string' && userId !== '') {
125
+ line.user = { id: userId };
126
+ delete line.userId;
127
+ enrichUser(line.user, userMap);
128
+ }
129
+ enrichTimestampsOf(line as Record<string, unknown>, [
130
+ 'created',
131
+ 'updated'
132
+ ]);
133
+ }
134
+ enrichTimestampsOf(snapshot as Record<string, unknown>, ['created']);
135
+ }
136
+
137
+ process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
138
+ };