@fink-andreas/pi-linear-tools 0.4.1 → 0.4.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/CHANGELOG.md +37 -0
- package/README.md +139 -13
- package/extensions/pi-linear-tools.js +298 -60
- package/index.js +1 -951
- package/package.json +6 -2
- package/src/cli.js +703 -7
- package/src/handlers.js +429 -4
- package/src/linear-client.js +7 -2
- package/src/linear.js +1502 -152
- package/src/sync-doc.js +1208 -0
- package/FUNCTIONALITY.md +0 -57
- package/POST_RELEASE_CHECKLIST.md +0 -30
- package/RELEASE.md +0 -50
package/src/cli.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { loadSettings, saveSettings } from './settings.js';
|
|
2
2
|
import { createLinearClient } from './linear-client.js';
|
|
3
3
|
import { resolveProjectRef } from './linear.js';
|
|
4
|
+
import {
|
|
5
|
+
explainSyncDocSetup,
|
|
6
|
+
initSyncDocConfig,
|
|
7
|
+
listSyncDocTargets,
|
|
8
|
+
runAllSyncDocs,
|
|
9
|
+
runSyncDoc,
|
|
10
|
+
} from './sync-doc.js';
|
|
4
11
|
import {
|
|
5
12
|
authenticate,
|
|
6
13
|
logout,
|
|
@@ -11,12 +18,25 @@ import {
|
|
|
11
18
|
import {
|
|
12
19
|
executeIssueList,
|
|
13
20
|
executeIssueView,
|
|
21
|
+
executeIssueActivity,
|
|
14
22
|
executeIssueCreate,
|
|
15
23
|
executeIssueUpdate,
|
|
16
24
|
executeIssueComment,
|
|
17
25
|
executeIssueStart,
|
|
18
26
|
executeIssueDelete,
|
|
19
27
|
executeProjectList,
|
|
28
|
+
executeProjectView,
|
|
29
|
+
executeProjectCreate,
|
|
30
|
+
executeProjectUpdate,
|
|
31
|
+
executeProjectDelete,
|
|
32
|
+
executeProjectArchive,
|
|
33
|
+
executeProjectUnarchive,
|
|
34
|
+
executeProjectUpdateList,
|
|
35
|
+
executeProjectUpdateView,
|
|
36
|
+
executeProjectUpdateCreate,
|
|
37
|
+
executeProjectUpdateUpdate,
|
|
38
|
+
executeProjectUpdateArchive,
|
|
39
|
+
executeProjectUpdateUnarchive,
|
|
20
40
|
executeTeamList,
|
|
21
41
|
executeMilestoneList,
|
|
22
42
|
executeMilestoneView,
|
|
@@ -131,8 +151,10 @@ Usage:
|
|
|
131
151
|
pi-linear-tools <command> [options]
|
|
132
152
|
|
|
133
153
|
Commands:
|
|
134
|
-
issue <action> [options] Manage issues
|
|
135
|
-
project <action> [options] Manage projects
|
|
154
|
+
issue <action> [options] Manage issues, comments, and issue activity/history
|
|
155
|
+
project <action> [options] Manage projects and project metadata
|
|
156
|
+
project-update <action> [options] Manage project updates (Linear Updates tab entries)
|
|
157
|
+
sync-doc [action] [options] Sync local markdown into Linear fields
|
|
136
158
|
team <action> [options] Manage teams
|
|
137
159
|
milestone <action> [options] Manage milestones
|
|
138
160
|
|
|
@@ -149,8 +171,9 @@ Auth Actions:
|
|
|
149
171
|
status Show current authentication status
|
|
150
172
|
|
|
151
173
|
Issue Actions:
|
|
152
|
-
list [--project X] [--states X,Y] [--assignee me|all] [--limit N]
|
|
174
|
+
list [--project X] [--states X,Y] [--assignee me|all] [--team X] [--limit N]
|
|
153
175
|
view <issue> [--no-comments]
|
|
176
|
+
activity <issue> [--limit N] [--include-archived true|false]
|
|
154
177
|
create --title X [--team X] [--project X] [--description X] [--priority 0-4] [--assignee me|ID]
|
|
155
178
|
update <issue> [--title X] [--description X] [--state X] [--priority 0-4]
|
|
156
179
|
[--assignee me|ID] [--milestone X] [--sub-issue-of X]
|
|
@@ -160,6 +183,20 @@ Issue Actions:
|
|
|
160
183
|
|
|
161
184
|
Project Actions:
|
|
162
185
|
list
|
|
186
|
+
view <project>
|
|
187
|
+
create --name X --teams ENG,OPS [--description X] [--lead me|ID] [--priority 0-4] [--target-date YYYY-MM-DD]
|
|
188
|
+
update <project> [--name X] [--description X] [--teams X,Y] [--lead me|none|ID] [--target-date YYYY-MM-DD]
|
|
189
|
+
delete <project>
|
|
190
|
+
archive <project>
|
|
191
|
+
unarchive <project>
|
|
192
|
+
|
|
193
|
+
Project Update Actions:
|
|
194
|
+
list --project X [--limit N] [--include-archived true|false]
|
|
195
|
+
view <project-update-id>
|
|
196
|
+
create --project X [--body X] [--health onTrack|atRisk|offTrack]
|
|
197
|
+
update <project-update-id> [--body X] [--health onTrack|atRisk|offTrack]
|
|
198
|
+
archive <project-update-id>
|
|
199
|
+
unarchive <project-update-id>
|
|
163
200
|
|
|
164
201
|
Team Actions:
|
|
165
202
|
list
|
|
@@ -171,6 +208,36 @@ Milestone Actions:
|
|
|
171
208
|
update <milestone-id> [--name X] [--description X] [--target-date X] [--status X]
|
|
172
209
|
delete <milestone-id>
|
|
173
210
|
|
|
211
|
+
Sync Doc Actions:
|
|
212
|
+
init [--cwd X] [--project X] [--file X]
|
|
213
|
+
explain
|
|
214
|
+
list [--config X]
|
|
215
|
+
run [--target X] [--config X]
|
|
216
|
+
check [--target X] [--config X]
|
|
217
|
+
run --file X --project X [--field content|description] [--marker X]
|
|
218
|
+
run --file X --issue X [--field description] [--marker X]
|
|
219
|
+
|
|
220
|
+
Command Notes:
|
|
221
|
+
issue update changes issue fields; issue activity reads the Activity timeline.
|
|
222
|
+
project update changes project fields; project-update manages Updates tab entries.
|
|
223
|
+
project-update maps to Linear project updates in the Updates tab.
|
|
224
|
+
sync-doc init scaffolds .linear-tools/config.json in the target folder.
|
|
225
|
+
sync-doc run/check defaults to all configured targets in .linear-tools/config.json.
|
|
226
|
+
sync-doc --target X narrows the operation to one configured target.
|
|
227
|
+
|
|
228
|
+
More Help:
|
|
229
|
+
pi-linear-tools issue --help
|
|
230
|
+
pi-linear-tools project --help
|
|
231
|
+
pi-linear-tools project-update --help
|
|
232
|
+
pi-linear-tools sync-doc --help
|
|
233
|
+
pi-linear-tools milestone --help
|
|
234
|
+
|
|
235
|
+
Reference Conventions:
|
|
236
|
+
issues issue key (ENG-123) or issue ID
|
|
237
|
+
projects project name or project ID
|
|
238
|
+
project-updates project update ID
|
|
239
|
+
milestones milestone ID
|
|
240
|
+
|
|
174
241
|
Common Flags:
|
|
175
242
|
--project Project name or ID
|
|
176
243
|
--team Team key (e.g., ENG)
|
|
@@ -182,10 +249,21 @@ Common Flags:
|
|
|
182
249
|
Examples:
|
|
183
250
|
pi-linear-tools auth login
|
|
184
251
|
pi-linear-tools auth status
|
|
252
|
+
pi-linear-tools issue update ENG-123 --state "In Progress" --assignee me
|
|
253
|
+
pi-linear-tools issue comment ENG-123 --body "Blocked on API review"
|
|
254
|
+
pi-linear-tools issue activity ENG-123 --limit 20
|
|
255
|
+
pi-linear-tools project view "Roadmap Refresh"
|
|
256
|
+
pi-linear-tools project update "Roadmap Refresh" --target-date 2026-04-15 --lead me
|
|
257
|
+
pi-linear-tools project-update create --project "Roadmap Refresh" --body "Weekly update" --health onTrack
|
|
258
|
+
pi-linear-tools project-update update 12345678-1234-1234-1234-123456789abc --health atRisk
|
|
259
|
+
pi-linear-tools sync-doc list
|
|
260
|
+
pi-linear-tools sync-doc init --cwd /path/to/subproject --project "Project name or ID"
|
|
261
|
+
pi-linear-tools sync-doc explain
|
|
262
|
+
pi-linear-tools sync-doc run
|
|
263
|
+
pi-linear-tools sync-doc check
|
|
185
264
|
pi-linear-tools issue list --project MyProject --states "In Progress,Backlog"
|
|
186
265
|
pi-linear-tools issue view ENG-123
|
|
187
266
|
pi-linear-tools issue create --title "Fix bug" --team ENG --priority 2
|
|
188
|
-
pi-linear-tools issue update ENG-123 --state "In Progress" --assignee me
|
|
189
267
|
pi-linear-tools issue start ENG-123
|
|
190
268
|
pi-linear-tools milestone list --project MyProject
|
|
191
269
|
pi-linear-tools config --api-key lin_xxx
|
|
@@ -204,9 +282,15 @@ function printIssueHelp() {
|
|
|
204
282
|
Usage:
|
|
205
283
|
pi-linear-tools issue <action> [options]
|
|
206
284
|
|
|
285
|
+
Guidance:
|
|
286
|
+
Use "update" to change the issue itself: title, description, state, assignee, milestone, or parent.
|
|
287
|
+
Use "comment" to add a discussion entry.
|
|
288
|
+
Use "activity" to read the Activity timeline shown in Linear.
|
|
289
|
+
|
|
207
290
|
Actions:
|
|
208
291
|
list List issues in a project
|
|
209
292
|
view View issue details
|
|
293
|
+
activity View issue activity/history
|
|
210
294
|
create Create a new issue
|
|
211
295
|
update Update an existing issue
|
|
212
296
|
comment Add a comment to an issue
|
|
@@ -217,12 +301,18 @@ List Options:
|
|
|
217
301
|
--project X Project name or ID (default: current directory name)
|
|
218
302
|
--states X,Y Filter by state names (comma-separated)
|
|
219
303
|
--assignee X Filter by assignee: "me" or "all"
|
|
304
|
+
--team X Filter by team key (e.g., ENG) or ID
|
|
220
305
|
--limit N Max results (default: 50)
|
|
221
306
|
|
|
222
307
|
View Options:
|
|
223
|
-
<issue> Issue key (e.g., ENG-123) or
|
|
308
|
+
<issue> Issue key (e.g., ENG-123), ID, or issue URL
|
|
224
309
|
--no-comments Exclude comments from output
|
|
225
310
|
|
|
311
|
+
Activity Options:
|
|
312
|
+
<issue> Issue key, ID, or issue URL
|
|
313
|
+
--limit N Max activity entries to fetch (default: 20)
|
|
314
|
+
--include-archived X true or false
|
|
315
|
+
|
|
226
316
|
Create Options:
|
|
227
317
|
--title X Issue title (required)
|
|
228
318
|
--team X Team key, e.g., ENG (required if no default team)
|
|
@@ -253,6 +343,14 @@ Start Options:
|
|
|
253
343
|
|
|
254
344
|
Delete Options:
|
|
255
345
|
<issue> Issue key or ID
|
|
346
|
+
|
|
347
|
+
Examples:
|
|
348
|
+
pi-linear-tools issue view ENG-123
|
|
349
|
+
pi-linear-tools issue update ENG-123 --state "In Progress" --assignee me
|
|
350
|
+
pi-linear-tools issue update ENG-123 --milestone "Sprint 12" --priority 2
|
|
351
|
+
pi-linear-tools issue comment ENG-123 --body "Ready for review"
|
|
352
|
+
pi-linear-tools issue activity ENG-123 --limit 20
|
|
353
|
+
pi-linear-tools issue activity https://linear.app/workspace/issue/ENG-123/example --limit 20
|
|
256
354
|
`);
|
|
257
355
|
}
|
|
258
356
|
|
|
@@ -260,10 +358,175 @@ function printProjectHelp() {
|
|
|
260
358
|
console.log(`pi-linear-tools project - Manage Linear projects
|
|
261
359
|
|
|
262
360
|
Usage:
|
|
263
|
-
pi-linear-tools project <action>
|
|
361
|
+
pi-linear-tools project <action> [options]
|
|
362
|
+
|
|
363
|
+
Guidance:
|
|
364
|
+
Use "project update" to change the project record itself: name, teams, dates, lead, description, priority.
|
|
365
|
+
Use "project-update" for the Updates tab entries that appear as weekly or status updates in Linear.
|
|
366
|
+
|
|
367
|
+
Actions:
|
|
368
|
+
list List all accessible projects
|
|
369
|
+
view View project details
|
|
370
|
+
create Create a new project
|
|
371
|
+
update Update an existing project
|
|
372
|
+
delete Delete a project
|
|
373
|
+
archive Archive a project
|
|
374
|
+
unarchive Restore an archived project
|
|
375
|
+
|
|
376
|
+
View Options:
|
|
377
|
+
<project> Project name or ID
|
|
378
|
+
|
|
379
|
+
Create Options:
|
|
380
|
+
--name X Project name (required)
|
|
381
|
+
--teams X,Y Team keys or IDs (required)
|
|
382
|
+
--description X Project description
|
|
383
|
+
--lead X "me" or user ID
|
|
384
|
+
--priority N Priority 0-4
|
|
385
|
+
--color X Project color (hex)
|
|
386
|
+
--icon X Project icon
|
|
387
|
+
--start-date X Planned start date (YYYY-MM-DD)
|
|
388
|
+
--target-date X Planned target date (YYYY-MM-DD)
|
|
389
|
+
|
|
390
|
+
Update Options:
|
|
391
|
+
<project> Project name or ID
|
|
392
|
+
--name X New name
|
|
393
|
+
--teams X,Y Replace associated teams
|
|
394
|
+
--description X New description
|
|
395
|
+
--lead X "me", "none", or user ID
|
|
396
|
+
--priority N New priority 0-4
|
|
397
|
+
--color X New project color (hex)
|
|
398
|
+
--icon X New project icon
|
|
399
|
+
--start-date X New planned start date
|
|
400
|
+
--target-date X New planned target date
|
|
401
|
+
|
|
402
|
+
Delete Options:
|
|
403
|
+
<project> Project name or ID
|
|
404
|
+
|
|
405
|
+
Archive Options:
|
|
406
|
+
<project> Project name or ID
|
|
407
|
+
|
|
408
|
+
Unarchive Options:
|
|
409
|
+
<project> Project name or ID
|
|
410
|
+
|
|
411
|
+
Examples:
|
|
412
|
+
pi-linear-tools project view "Roadmap Refresh"
|
|
413
|
+
pi-linear-tools project update "Roadmap Refresh" --description "New scope" --target-date 2026-04-15
|
|
414
|
+
pi-linear-tools project update "Roadmap Refresh" --lead me --teams ENG,OPS
|
|
415
|
+
pi-linear-tools project archive "Roadmap Refresh"
|
|
416
|
+
`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function printSyncDocHelp() {
|
|
420
|
+
console.log(`pi-linear-tools sync-doc - Sync markdown files into Linear
|
|
421
|
+
|
|
422
|
+
Usage:
|
|
423
|
+
pi-linear-tools sync-doc [init|explain|list|run|check] [options]
|
|
424
|
+
|
|
425
|
+
Config:
|
|
426
|
+
Reads targets from .linear-tools/config.json in the current project tree and ~/.linear-tools/config.json.
|
|
427
|
+
Put config in the smallest folder that owns the docs being synced. The nearest config wins.
|
|
428
|
+
Use repo root only for targets intentionally shared across multiple subprojects. Use ~/.linear-tools/config.json for personal defaults.
|
|
429
|
+
|
|
430
|
+
Actions:
|
|
431
|
+
init Scaffold a starter .linear-tools/config.json in the target folder
|
|
432
|
+
explain Print the recommended config placement and overview/document model
|
|
433
|
+
list Show resolved sync targets from config
|
|
434
|
+
run Update Linear if the managed block differs. Defaults to all configured targets.
|
|
435
|
+
check Show whether a sync would change Linear. Defaults to all configured targets.
|
|
436
|
+
|
|
437
|
+
Target Options:
|
|
438
|
+
--target X Target name from config
|
|
439
|
+
--config X Explicit path to config.json or .linear-tools/
|
|
440
|
+
--cwd X Resolve config and files relative to X
|
|
441
|
+
|
|
442
|
+
Init Options:
|
|
443
|
+
--cwd X Create .linear-tools/config.json inside X (defaults to current directory)
|
|
444
|
+
--project X Fill the starter overview target with a project name or ID
|
|
445
|
+
--file X File path for the starter overview target (default: README.md)
|
|
446
|
+
--field X content or description (default: content)
|
|
447
|
+
--force Overwrite an existing config.json
|
|
448
|
+
|
|
449
|
+
One-off Run/Check Options:
|
|
450
|
+
--file X Markdown file to sync
|
|
451
|
+
--project X Linear project name, id, slug, or project URL
|
|
452
|
+
--issue X Linear issue key or id
|
|
453
|
+
--target-type X projectField, issueField, or document
|
|
454
|
+
--document-title X Required for one-off document targets unless inferred from file name
|
|
455
|
+
--document-id X Existing Linear document ID or URL identifier
|
|
456
|
+
--field X For projects: content or description. For issues: description
|
|
457
|
+
--marker X Managed block marker (default: file basename without extension)
|
|
458
|
+
|
|
459
|
+
Managed Block:
|
|
460
|
+
<!-- linear-tools:sync-start MARKER -->
|
|
461
|
+
...generated content...
|
|
462
|
+
<!-- linear-tools:sync-end MARKER -->
|
|
463
|
+
|
|
464
|
+
Project Document Index:
|
|
465
|
+
A projectField target can set documentIndexMarker to maintain a managed list of synced document targets.
|
|
466
|
+
Use this for one overview doc in the project body plus separate linked Linear documents for deeper docs.
|
|
467
|
+
|
|
468
|
+
Examples:
|
|
469
|
+
pi-linear-tools sync-doc init --cwd /path/to/subproject --project "Project name or ID"
|
|
470
|
+
pi-linear-tools sync-doc explain
|
|
471
|
+
pi-linear-tools sync-doc list
|
|
472
|
+
pi-linear-tools sync-doc run
|
|
473
|
+
pi-linear-tools sync-doc check
|
|
474
|
+
pi-linear-tools sync-doc run --target project-overview
|
|
475
|
+
pi-linear-tools sync-doc check --target project-overview
|
|
476
|
+
pi-linear-tools sync-doc run --file README.md --project "Project name or ID" --field content
|
|
477
|
+
pi-linear-tools sync-doc run --file docs/provider.md --project "Project name or ID" --target-type document --document-title "Provider Doc"
|
|
478
|
+
`);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function printProjectUpdateHelp() {
|
|
482
|
+
console.log(`pi-linear-tools project-update - Manage Linear project updates
|
|
483
|
+
|
|
484
|
+
Usage:
|
|
485
|
+
pi-linear-tools project-update <action> [options]
|
|
486
|
+
|
|
487
|
+
Guidance:
|
|
488
|
+
This command manages the entries shown in a project's Updates tab.
|
|
489
|
+
Create an update by project name or ID, then use the returned project update ID for later view/update/archive actions.
|
|
264
490
|
|
|
265
491
|
Actions:
|
|
266
|
-
list
|
|
492
|
+
list List updates for a project
|
|
493
|
+
view View project update details
|
|
494
|
+
create Create a new project update
|
|
495
|
+
update Update an existing project update
|
|
496
|
+
archive Archive a project update
|
|
497
|
+
unarchive Restore an archived project update
|
|
498
|
+
|
|
499
|
+
List Options:
|
|
500
|
+
--project X Project name or ID (required)
|
|
501
|
+
--limit N Max results (default: 10)
|
|
502
|
+
--include-archived X true or false
|
|
503
|
+
|
|
504
|
+
View Options:
|
|
505
|
+
<project-update-id> Project update ID
|
|
506
|
+
|
|
507
|
+
Create Options:
|
|
508
|
+
--project X Project name or ID (required)
|
|
509
|
+
--body X Update body in markdown
|
|
510
|
+
--health X onTrack, atRisk, or offTrack
|
|
511
|
+
--is-diff-hidden X true or false
|
|
512
|
+
|
|
513
|
+
Update Options:
|
|
514
|
+
<project-update-id> Project update ID
|
|
515
|
+
--body X Updated body in markdown
|
|
516
|
+
--health X onTrack, atRisk, or offTrack
|
|
517
|
+
--is-diff-hidden X true or false
|
|
518
|
+
|
|
519
|
+
Archive Options:
|
|
520
|
+
<project-update-id> Project update ID
|
|
521
|
+
|
|
522
|
+
Unarchive Options:
|
|
523
|
+
<project-update-id> Project update ID
|
|
524
|
+
|
|
525
|
+
Examples:
|
|
526
|
+
pi-linear-tools project-update list --project "Roadmap Refresh"
|
|
527
|
+
pi-linear-tools project-update create --project "Roadmap Refresh" --body "Weekly update" --health onTrack
|
|
528
|
+
pi-linear-tools project-update update 12345678-1234-1234-1234-123456789abc --body "Revised update" --health atRisk
|
|
529
|
+
pi-linear-tools project-update view 12345678-1234-1234-1234-123456789abc
|
|
267
530
|
`);
|
|
268
531
|
}
|
|
269
532
|
|
|
@@ -531,6 +794,7 @@ async function handleIssueList(args) {
|
|
|
531
794
|
project: readFlag(args, '--project'),
|
|
532
795
|
states: parseArrayValue(readFlag(args, '--states')),
|
|
533
796
|
assignee: readFlag(args, '--assignee'),
|
|
797
|
+
team: readFlag(args, '--team'),
|
|
534
798
|
limit: parseNumber(readFlag(args, '--limit')),
|
|
535
799
|
};
|
|
536
800
|
|
|
@@ -555,6 +819,24 @@ async function handleIssueView(args) {
|
|
|
555
819
|
console.log(result.content[0].text);
|
|
556
820
|
}
|
|
557
821
|
|
|
822
|
+
async function handleIssueActivity(args) {
|
|
823
|
+
const client = await createAuthenticatedClient();
|
|
824
|
+
|
|
825
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
826
|
+
if (positional.length === 0) {
|
|
827
|
+
throw new Error('Missing required argument: issue key or ID');
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const params = {
|
|
831
|
+
issue: positional[0],
|
|
832
|
+
limit: parseNumber(readFlag(args, '--limit')),
|
|
833
|
+
includeArchived: parseBoolean(readFlag(args, '--include-archived')),
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
const result = await executeIssueActivity(client, params);
|
|
837
|
+
console.log(result.content[0].text);
|
|
838
|
+
}
|
|
839
|
+
|
|
558
840
|
async function handleIssueCreate(args) {
|
|
559
841
|
const client = await createAuthenticatedClient();
|
|
560
842
|
|
|
@@ -668,6 +950,8 @@ async function handleIssue(args) {
|
|
|
668
950
|
return handleIssueList(rest);
|
|
669
951
|
case 'view':
|
|
670
952
|
return handleIssueView(rest);
|
|
953
|
+
case 'activity':
|
|
954
|
+
return handleIssueActivity(rest);
|
|
671
955
|
case 'create':
|
|
672
956
|
return handleIssueCreate(rest);
|
|
673
957
|
case 'update':
|
|
@@ -692,6 +976,107 @@ async function handleProjectList() {
|
|
|
692
976
|
console.log(result.content[0].text);
|
|
693
977
|
}
|
|
694
978
|
|
|
979
|
+
async function handleProjectView(args) {
|
|
980
|
+
const client = await createAuthenticatedClient();
|
|
981
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
982
|
+
if (positional.length === 0) {
|
|
983
|
+
throw new Error('Missing required argument: project name or ID');
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const result = await executeProjectView(client, {
|
|
987
|
+
project: positional[0],
|
|
988
|
+
});
|
|
989
|
+
console.log(result.content[0].text);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
async function handleProjectCreate(args) {
|
|
993
|
+
const client = await createAuthenticatedClient();
|
|
994
|
+
const params = {
|
|
995
|
+
name: readFlag(args, '--name'),
|
|
996
|
+
teams: parseArrayValue(readFlag(args, '--teams')),
|
|
997
|
+
description: readFlag(args, '--description'),
|
|
998
|
+
lead: readFlag(args, '--lead'),
|
|
999
|
+
priority: parseNumber(readFlag(args, '--priority')),
|
|
1000
|
+
color: readFlag(args, '--color'),
|
|
1001
|
+
icon: readFlag(args, '--icon'),
|
|
1002
|
+
startDate: readFlag(args, '--start-date'),
|
|
1003
|
+
targetDate: readFlag(args, '--target-date'),
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
if (!params.name) {
|
|
1007
|
+
throw new Error('Missing required flag: --name');
|
|
1008
|
+
}
|
|
1009
|
+
if (!params.teams || params.teams.length === 0) {
|
|
1010
|
+
throw new Error('Missing required flag: --teams');
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const result = await executeProjectCreate(client, params);
|
|
1014
|
+
console.log(result.content[0].text);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
async function handleProjectUpdate(args) {
|
|
1018
|
+
const client = await createAuthenticatedClient();
|
|
1019
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
1020
|
+
if (positional.length === 0) {
|
|
1021
|
+
throw new Error('Missing required argument: project name or ID');
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const params = {
|
|
1025
|
+
project: positional[0],
|
|
1026
|
+
name: readFlag(args, '--name'),
|
|
1027
|
+
teams: parseArrayValue(readFlag(args, '--teams')),
|
|
1028
|
+
description: readFlag(args, '--description'),
|
|
1029
|
+
lead: readFlag(args, '--lead'),
|
|
1030
|
+
priority: parseNumber(readFlag(args, '--priority')),
|
|
1031
|
+
color: readFlag(args, '--color'),
|
|
1032
|
+
icon: readFlag(args, '--icon'),
|
|
1033
|
+
startDate: readFlag(args, '--start-date'),
|
|
1034
|
+
targetDate: readFlag(args, '--target-date'),
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
const result = await executeProjectUpdate(client, params);
|
|
1038
|
+
console.log(result.content[0].text);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
async function handleProjectDelete(args) {
|
|
1042
|
+
const client = await createAuthenticatedClient();
|
|
1043
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
1044
|
+
if (positional.length === 0) {
|
|
1045
|
+
throw new Error('Missing required argument: project name or ID');
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
const result = await executeProjectDelete(client, {
|
|
1049
|
+
project: positional[0],
|
|
1050
|
+
});
|
|
1051
|
+
console.log(result.content[0].text);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
async function handleProjectArchive(args) {
|
|
1055
|
+
const client = await createAuthenticatedClient();
|
|
1056
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
1057
|
+
if (positional.length === 0) {
|
|
1058
|
+
throw new Error('Missing required argument: project name or ID');
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const result = await executeProjectArchive(client, {
|
|
1062
|
+
project: positional[0],
|
|
1063
|
+
});
|
|
1064
|
+
console.log(result.content[0].text);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
async function handleProjectUnarchive(args) {
|
|
1068
|
+
const client = await createAuthenticatedClient();
|
|
1069
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
1070
|
+
if (positional.length === 0) {
|
|
1071
|
+
throw new Error('Missing required argument: project name or ID');
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const result = await executeProjectUnarchive(client, {
|
|
1075
|
+
project: positional[0],
|
|
1076
|
+
});
|
|
1077
|
+
console.log(result.content[0].text);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
695
1080
|
async function handleProject(args) {
|
|
696
1081
|
const [action] = args;
|
|
697
1082
|
|
|
@@ -703,11 +1088,312 @@ async function handleProject(args) {
|
|
|
703
1088
|
switch (action) {
|
|
704
1089
|
case 'list':
|
|
705
1090
|
return handleProjectList();
|
|
1091
|
+
case 'view':
|
|
1092
|
+
return handleProjectView(args.slice(1));
|
|
1093
|
+
case 'create':
|
|
1094
|
+
return handleProjectCreate(args.slice(1));
|
|
1095
|
+
case 'update':
|
|
1096
|
+
return handleProjectUpdate(args.slice(1));
|
|
1097
|
+
case 'delete':
|
|
1098
|
+
return handleProjectDelete(args.slice(1));
|
|
1099
|
+
case 'archive':
|
|
1100
|
+
return handleProjectArchive(args.slice(1));
|
|
1101
|
+
case 'unarchive':
|
|
1102
|
+
return handleProjectUnarchive(args.slice(1));
|
|
706
1103
|
default:
|
|
707
1104
|
throw new Error(`Unknown project action: ${action}`);
|
|
708
1105
|
}
|
|
709
1106
|
}
|
|
710
1107
|
|
|
1108
|
+
// ===== PROJECT UPDATE HANDLERS =====
|
|
1109
|
+
|
|
1110
|
+
async function handleProjectUpdateListCli(args) {
|
|
1111
|
+
const client = await createAuthenticatedClient();
|
|
1112
|
+
const params = {
|
|
1113
|
+
project: readFlag(args, '--project'),
|
|
1114
|
+
limit: parseNumber(readFlag(args, '--limit')),
|
|
1115
|
+
includeArchived: parseBoolean(readFlag(args, '--include-archived')),
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
if (!params.project) {
|
|
1119
|
+
throw new Error('Missing required flag: --project');
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
const result = await executeProjectUpdateList(client, params);
|
|
1123
|
+
console.log(result.content[0].text);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function printSyncDocResult(result) {
|
|
1127
|
+
if (!result.changed) {
|
|
1128
|
+
if (result.targetType === 'document') {
|
|
1129
|
+
console.log(`No sync changes needed for document "${result.entityName}" from ${result.file}`);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
console.log(`No sync changes needed for ${result.entityType} "${result.entityName}" field "${result.field}" from ${result.file}`);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (result.mode === 'check') {
|
|
1137
|
+
if (result.targetType === 'document') {
|
|
1138
|
+
console.log(`Sync needed for document "${result.entityName}" from ${result.file}`);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
console.log(`Sync needed for ${result.entityType} "${result.entityName}" field "${result.field}" from ${result.file}`);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
if (result.targetType === 'document') {
|
|
1146
|
+
console.log(`Synced ${result.file} to document "${result.entityName}" using marker "${result.marker}"`);
|
|
1147
|
+
if (result.documentUrl) {
|
|
1148
|
+
console.log(result.documentUrl);
|
|
1149
|
+
}
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
console.log(`Synced ${result.file} to ${result.entityType} "${result.entityName}" field "${result.field}" using marker "${result.marker}"`);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function printSyncDocTargets(result) {
|
|
1157
|
+
if (!Array.isArray(result.targets) || result.targets.length === 0) {
|
|
1158
|
+
console.log('No sync-doc targets configured.');
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const lines = result.targets.map((target) => {
|
|
1163
|
+
if (target.targetType === 'document') {
|
|
1164
|
+
const entityLabel = `${target.issue ? 'issue' : 'project'}:${target.entityRef}`;
|
|
1165
|
+
return `- ${target.name} -> document "${target.title}" linked to ${entityLabel} from ${target.file} (marker: ${target.marker})`;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const entityLabel = `${target.targetType === 'issueField' ? 'issue' : 'project'}:${target.entityRef}`;
|
|
1169
|
+
const extra = target.documentIndexMarker ? `, document index: ${target.documentIndexMarker}` : '';
|
|
1170
|
+
return `- ${target.name} -> ${entityLabel} field "${target.field}" from ${target.file} (marker: ${target.marker}${extra})`;
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
if (result.configPath) {
|
|
1174
|
+
console.log(`Resolved sync-doc targets from ${result.configPath}`);
|
|
1175
|
+
}
|
|
1176
|
+
console.log(lines.join('\n'));
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function printSyncDocBatchResult(result) {
|
|
1180
|
+
const header = result.mode === 'check'
|
|
1181
|
+
? `Checked ${result.total} sync-doc target(s): ${result.changedCount} need updates, ${result.unchangedCount} unchanged`
|
|
1182
|
+
: `Ran ${result.total} sync-doc target(s): ${result.changedCount} updated, ${result.unchangedCount} unchanged`;
|
|
1183
|
+
const lines = result.results.map((entry) => {
|
|
1184
|
+
const status = entry.changed ? (result.mode === 'check' ? 'needs-sync' : 'synced') : 'unchanged';
|
|
1185
|
+
if (entry.targetType === 'document') {
|
|
1186
|
+
return `- [${status}] ${entry.targetName} -> document "${entry.entityName}" from ${entry.file}`;
|
|
1187
|
+
}
|
|
1188
|
+
return `- [${status}] ${entry.targetName} -> ${entry.entityType} "${entry.entityName}" field "${entry.field}" from ${entry.file}`;
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
console.log([header, ...lines].join('\n'));
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function printSyncDocInitResult(result) {
|
|
1195
|
+
if (!result.created) {
|
|
1196
|
+
console.log(`Sync-doc config already exists at ${result.configPath}`);
|
|
1197
|
+
console.log('Use --force to overwrite it, or edit the existing config manually.');
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
console.log(`${result.overwritten ? 'Rewrote' : 'Created'} ${result.configPath}`);
|
|
1202
|
+
console.log(`State will be written to ${result.statePath}`);
|
|
1203
|
+
if (result.target) {
|
|
1204
|
+
console.log(`Starter target: ${result.target.name} -> project:${result.target.project} field "${result.target.field}" from ${result.target.file}`);
|
|
1205
|
+
}
|
|
1206
|
+
console.log('Next steps:');
|
|
1207
|
+
console.log(`- Edit ${result.configPath} to add document targets for deeper docs`);
|
|
1208
|
+
console.log(`- Run pi-linear-tools sync-doc list --cwd ${result.cwd}`);
|
|
1209
|
+
console.log(`- Run pi-linear-tools sync-doc run --cwd ${result.cwd}`);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
async function handleSyncDocCommand(args) {
|
|
1213
|
+
const [maybeAction, ...restArgs] = args;
|
|
1214
|
+
const action = !maybeAction || maybeAction.startsWith('-') ? 'run' : maybeAction;
|
|
1215
|
+
const commandArgs = !maybeAction || maybeAction.startsWith('-') ? args : restArgs;
|
|
1216
|
+
const cwd = readFlag(commandArgs, '--cwd') || process.cwd();
|
|
1217
|
+
|
|
1218
|
+
if (
|
|
1219
|
+
action === '--help'
|
|
1220
|
+
|| action === '-h'
|
|
1221
|
+
|| action === 'help'
|
|
1222
|
+
|| hasFlag(commandArgs, '--help')
|
|
1223
|
+
|| hasFlag(commandArgs, '-h')
|
|
1224
|
+
) {
|
|
1225
|
+
printSyncDocHelp();
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
if (!['init', 'explain', 'list', 'run', 'check'].includes(action)) {
|
|
1230
|
+
throw new Error(`Unknown sync-doc action: ${action}`);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
if (action === 'explain') {
|
|
1234
|
+
console.log(explainSyncDocSetup());
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
if (action === 'init') {
|
|
1239
|
+
const result = await initSyncDocConfig({
|
|
1240
|
+
cwd,
|
|
1241
|
+
file: readFlag(commandArgs, '--file'),
|
|
1242
|
+
project: readFlag(commandArgs, '--project'),
|
|
1243
|
+
field: readFlag(commandArgs, '--field'),
|
|
1244
|
+
marker: readFlag(commandArgs, '--marker'),
|
|
1245
|
+
name: readFlag(commandArgs, '--name'),
|
|
1246
|
+
documentIndexMarker: readFlag(commandArgs, '--document-index-marker'),
|
|
1247
|
+
force: hasFlag(commandArgs, '--force'),
|
|
1248
|
+
});
|
|
1249
|
+
printSyncDocInitResult(result);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (action === 'list') {
|
|
1254
|
+
const result = await listSyncDocTargets({
|
|
1255
|
+
cwd,
|
|
1256
|
+
configPath: readFlag(commandArgs, '--config'),
|
|
1257
|
+
});
|
|
1258
|
+
printSyncDocTargets(result);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
const client = await createAuthenticatedClient();
|
|
1263
|
+
const hasOneOffFlags = Boolean(
|
|
1264
|
+
readFlag(commandArgs, '--file')
|
|
1265
|
+
&& (readFlag(commandArgs, '--project') || readFlag(commandArgs, '--issue'))
|
|
1266
|
+
);
|
|
1267
|
+
const hasNamedTarget = Boolean(readFlag(commandArgs, '--target'));
|
|
1268
|
+
|
|
1269
|
+
if (!hasOneOffFlags && !hasNamedTarget) {
|
|
1270
|
+
const result = await runAllSyncDocs(client, {
|
|
1271
|
+
mode: action,
|
|
1272
|
+
cwd,
|
|
1273
|
+
configPath: readFlag(commandArgs, '--config'),
|
|
1274
|
+
});
|
|
1275
|
+
printSyncDocBatchResult(result);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const result = await runSyncDoc(client, {
|
|
1280
|
+
mode: action,
|
|
1281
|
+
cwd,
|
|
1282
|
+
configPath: readFlag(commandArgs, '--config'),
|
|
1283
|
+
targetName: readFlag(commandArgs, '--target'),
|
|
1284
|
+
file: readFlag(commandArgs, '--file'),
|
|
1285
|
+
project: readFlag(commandArgs, '--project'),
|
|
1286
|
+
issue: readFlag(commandArgs, '--issue'),
|
|
1287
|
+
targetType: readFlag(commandArgs, '--target-type'),
|
|
1288
|
+
documentTitle: readFlag(commandArgs, '--document-title'),
|
|
1289
|
+
documentId: readFlag(commandArgs, '--document-id'),
|
|
1290
|
+
field: readFlag(commandArgs, '--field'),
|
|
1291
|
+
marker: readFlag(commandArgs, '--marker'),
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
printSyncDocResult(result);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
async function handleProjectUpdateViewCli(args) {
|
|
1298
|
+
const client = await createAuthenticatedClient();
|
|
1299
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
1300
|
+
if (positional.length === 0) {
|
|
1301
|
+
throw new Error('Missing required argument: project update ID');
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
const result = await executeProjectUpdateView(client, {
|
|
1305
|
+
projectUpdate: positional[0],
|
|
1306
|
+
});
|
|
1307
|
+
console.log(result.content[0].text);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
async function handleProjectUpdateCreateCli(args) {
|
|
1311
|
+
const client = await createAuthenticatedClient();
|
|
1312
|
+
const params = {
|
|
1313
|
+
project: readFlag(args, '--project'),
|
|
1314
|
+
body: readFlag(args, '--body'),
|
|
1315
|
+
health: readFlag(args, '--health'),
|
|
1316
|
+
isDiffHidden: parseBoolean(readFlag(args, '--is-diff-hidden')),
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
if (!params.project) {
|
|
1320
|
+
throw new Error('Missing required flag: --project');
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
const result = await executeProjectUpdateCreate(client, params);
|
|
1324
|
+
console.log(result.content[0].text);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
async function handleProjectUpdateUpdateCli(args) {
|
|
1328
|
+
const client = await createAuthenticatedClient();
|
|
1329
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
1330
|
+
if (positional.length === 0) {
|
|
1331
|
+
throw new Error('Missing required argument: project update ID');
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
const params = {
|
|
1335
|
+
projectUpdate: positional[0],
|
|
1336
|
+
body: readFlag(args, '--body'),
|
|
1337
|
+
health: readFlag(args, '--health'),
|
|
1338
|
+
isDiffHidden: parseBoolean(readFlag(args, '--is-diff-hidden')),
|
|
1339
|
+
};
|
|
1340
|
+
|
|
1341
|
+
const result = await executeProjectUpdateUpdate(client, params);
|
|
1342
|
+
console.log(result.content[0].text);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
async function handleProjectUpdateArchiveCli(args) {
|
|
1346
|
+
const client = await createAuthenticatedClient();
|
|
1347
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
1348
|
+
if (positional.length === 0) {
|
|
1349
|
+
throw new Error('Missing required argument: project update ID');
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
const result = await executeProjectUpdateArchive(client, {
|
|
1353
|
+
projectUpdate: positional[0],
|
|
1354
|
+
});
|
|
1355
|
+
console.log(result.content[0].text);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
async function handleProjectUpdateUnarchiveCli(args) {
|
|
1359
|
+
const client = await createAuthenticatedClient();
|
|
1360
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
1361
|
+
if (positional.length === 0) {
|
|
1362
|
+
throw new Error('Missing required argument: project update ID');
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
const result = await executeProjectUpdateUnarchive(client, {
|
|
1366
|
+
projectUpdate: positional[0],
|
|
1367
|
+
});
|
|
1368
|
+
console.log(result.content[0].text);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
async function handleProjectUpdateCommand(args) {
|
|
1372
|
+
const [action] = args;
|
|
1373
|
+
|
|
1374
|
+
if (!action || action === '--help' || action === '-h') {
|
|
1375
|
+
printProjectUpdateHelp();
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
switch (action) {
|
|
1380
|
+
case 'list':
|
|
1381
|
+
return handleProjectUpdateListCli(args.slice(1));
|
|
1382
|
+
case 'view':
|
|
1383
|
+
return handleProjectUpdateViewCli(args.slice(1));
|
|
1384
|
+
case 'create':
|
|
1385
|
+
return handleProjectUpdateCreateCli(args.slice(1));
|
|
1386
|
+
case 'update':
|
|
1387
|
+
return handleProjectUpdateUpdateCli(args.slice(1));
|
|
1388
|
+
case 'archive':
|
|
1389
|
+
return handleProjectUpdateArchiveCli(args.slice(1));
|
|
1390
|
+
case 'unarchive':
|
|
1391
|
+
return handleProjectUpdateUnarchiveCli(args.slice(1));
|
|
1392
|
+
default:
|
|
1393
|
+
throw new Error(`Unknown project-update action: ${action}`);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
711
1397
|
// ===== TEAM HANDLERS =====
|
|
712
1398
|
|
|
713
1399
|
async function handleTeamList() {
|
|
@@ -875,6 +1561,16 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
875
1561
|
return;
|
|
876
1562
|
}
|
|
877
1563
|
|
|
1564
|
+
if (command === 'project-update') {
|
|
1565
|
+
await handleProjectUpdateCommand(rest);
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
if (command === 'sync-doc') {
|
|
1570
|
+
await handleSyncDocCommand(rest);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
878
1574
|
if (command === 'team') {
|
|
879
1575
|
await handleTeam(rest);
|
|
880
1576
|
return;
|