@staff0rd/assist 0.62.0 → 0.64.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/README.md +3 -1
- package/claude/commands/journal.md +74 -0
- package/claude/commands/standup.md +35 -0
- package/claude/settings.json +6 -0
- package/dist/index.js +208 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,6 +36,8 @@ After installation, the `assist` command will be available globally.
|
|
|
36
36
|
- `/refactor` - Run refactoring checks for code quality
|
|
37
37
|
- `/restructure` - Analyze and restructure tightly-coupled files
|
|
38
38
|
- `/review-comments` - Process PR review comments one by one
|
|
39
|
+
- `/journal` - Append a journal entry summarising recent work, decisions, and notable observations
|
|
40
|
+
- `/standup` - Summarise recent journal entries as a standup update
|
|
39
41
|
- `/verify` - Run all verification commands in parallel
|
|
40
42
|
- `/transcript-format` - Format meeting transcripts from VTT files
|
|
41
43
|
- `/transcript-summarise` - Summarise transcripts missing summaries
|
|
@@ -57,7 +59,7 @@ After installation, the `assist` command will be available globally.
|
|
|
57
59
|
- `assist backlog add` - Add a new backlog item interactively
|
|
58
60
|
- `assist backlog start <id>` - Set a backlog item to in-progress
|
|
59
61
|
- `assist backlog done <id>` - Set a backlog item to done
|
|
60
|
-
- `assist roam auth` -
|
|
62
|
+
- `assist roam auth` - Authenticate with Roam via OAuth (opens browser, saves tokens to ~/.assist.yml)
|
|
61
63
|
- `assist run <name>` - Run a configured command from assist.yml
|
|
62
64
|
- `assist run add` - Add a new run configuration to assist.yml
|
|
63
65
|
- `assist config set <key> <value>` - Set a config value (e.g. commit.push true)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Append a journal entry summarising recent work, decisions, and notable observations
|
|
3
|
+
allowed_args: "[optional focus or note]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Append a structured journal entry to today's daily file. This should be fast — do not read existing journal files or attempt to deduplicate.
|
|
7
|
+
|
|
8
|
+
## Step 1: Determine the date and file
|
|
9
|
+
|
|
10
|
+
**IMPORTANT:** Do not rely on your own sense of the current date — it may be stale in long-running sessions. Always run `date +%Y-%m-%d` and `date +%H:%M` via Bash to get the actual current date and time.
|
|
11
|
+
|
|
12
|
+
The journal lives in `~/.claude/journal/`, one file per day, named `YYYY-MM-DD.md`.
|
|
13
|
+
|
|
14
|
+
If today's file doesn't exist, create it with a heading:
|
|
15
|
+
|
|
16
|
+
```markdown
|
|
17
|
+
# YYYY-MM-DD
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Step 2: Resolve project context
|
|
21
|
+
|
|
22
|
+
Determine the current project by matching the working directory against `~/.claude/skills/projects.json`. If not inside a registered project, use "general" as the project name.
|
|
23
|
+
|
|
24
|
+
## Step 3: Write the entry
|
|
25
|
+
|
|
26
|
+
Append an entry to the daily file. Each entry should follow this structure:
|
|
27
|
+
|
|
28
|
+
```markdown
|
|
29
|
+
## HH:MM — {project name} #{project name}
|
|
30
|
+
|
|
31
|
+
{Summary of what was done — keep it concise but capture the key points}
|
|
32
|
+
|
|
33
|
+
**Decisions:** {any notable decisions made and why, or "None"}
|
|
34
|
+
|
|
35
|
+
**Topics:** {flag anything blog-worthy, reusable, or worth revisiting — or "None"}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Tag the project name in the heading (e.g., `#project1`, `#general`). When flagging topics, prefix with `#blog-worthy`, `#reusable`, or `#demo-worthy`:
|
|
39
|
+
|
|
40
|
+
Guidelines:
|
|
41
|
+
|
|
42
|
+
- Summarise what was accomplished, not every step taken
|
|
43
|
+
- Capture the "why" behind decisions — this is what you'll forget
|
|
44
|
+
- Flag blog-worthy topics with a brief note on why it's interesting
|
|
45
|
+
- Flag reusable patterns, utilities, or approaches worth extracting
|
|
46
|
+
- Flag demo-worthy work — things that would make a good presentation, show-and-tell, or live walkthrough
|
|
47
|
+
- If the user provided `$ARGUMENTS`, use it to focus or annotate the entry
|
|
48
|
+
- Keep entries concise — a few lines per section, not paragraphs
|
|
49
|
+
|
|
50
|
+
## Step 4: Write detail files (when topics are flagged)
|
|
51
|
+
|
|
52
|
+
When a topic is flagged as blog-worthy or reusable, create a supporting detail file that captures the context needed to act on it later. Without this, the journal flags opportunities but loses the detail needed to follow through.
|
|
53
|
+
|
|
54
|
+
Detail files live in `~/.claude/journal/details/` and are named `YYYY-MM-DD-{slug}.md`.
|
|
55
|
+
|
|
56
|
+
Link them from the journal entry:
|
|
57
|
+
|
|
58
|
+
```markdown
|
|
59
|
+
**Topics:**
|
|
60
|
+
|
|
61
|
+
- #blog-worthy Teaching Claude to reflect — [detail](details/2026-02-13-reflect-workflow.md)
|
|
62
|
+
- #reusable The skill/hook pattern — [detail](details/2026-02-13-starter-kit.md)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Each detail file should include whichever of the following are relevant:
|
|
66
|
+
|
|
67
|
+
- **Context** — what problem was being solved and why
|
|
68
|
+
- **Approach** — what was tried, including dead ends and alternatives considered
|
|
69
|
+
- **Key code** — relevant snippets, patterns, or configurations that were created
|
|
70
|
+
- **Outcome** — what worked, what didn't, and why
|
|
71
|
+
- **Blog angle** — if blog-worthy, what makes it interesting to write about
|
|
72
|
+
- **Extraction notes** — if reusable, what could be extracted and how it might be generalised
|
|
73
|
+
|
|
74
|
+
These files are meant to preserve enough context that someone (including a future Claude session) could flesh out a blog post or extract reusable code without the original conversation.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Summarise recent journal entries as a standup update
|
|
3
|
+
allowed_args: "[days back, default 1] [project name]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Summarise recent journal entries.
|
|
7
|
+
|
|
8
|
+
## Step 1: Determine the range
|
|
9
|
+
|
|
10
|
+
Parse `$ARGUMENTS` for:
|
|
11
|
+
|
|
12
|
+
- A number (days to look back, default 1)
|
|
13
|
+
- A project name (filter to that project only, default all)
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
|
|
17
|
+
- `/standup` — yesterday's entries, all projects
|
|
18
|
+
- `/standup 3` — last 3 days, all projects
|
|
19
|
+
- `/standup project1` — yesterday's entries for project1
|
|
20
|
+
- `/standup 7 project2` — last 7 days for project2
|
|
21
|
+
|
|
22
|
+
## Step 2: Read journal files
|
|
23
|
+
|
|
24
|
+
Read the daily files from `~/.claude/journal/` for the date range. Files are named `YYYY-MM-DD.md`.
|
|
25
|
+
|
|
26
|
+
## Step 3: Present the summary
|
|
27
|
+
|
|
28
|
+
Summarise the entries concisely, grouped by day and project. Highlight:
|
|
29
|
+
|
|
30
|
+
- What was accomplished
|
|
31
|
+
- Key decisions made
|
|
32
|
+
- Any flagged blog topics or reusable IP
|
|
33
|
+
- Anything left in progress
|
|
34
|
+
|
|
35
|
+
Keep it brief — this is a standup, not a report.
|
package/claude/settings.json
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"Bash(assist transcript summarise:*)",
|
|
28
28
|
"Bash(assist complexity:*)",
|
|
29
29
|
"Bash(assist transcript format:*)",
|
|
30
|
+
"Bash(date:*)",
|
|
30
31
|
"Bash(git add:*)",
|
|
31
32
|
"Bash(git status:*)",
|
|
32
33
|
"Bash(git show:*)",
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"Bash(gh repo view:*)",
|
|
37
38
|
"Bash(gh pr checks:*)",
|
|
38
39
|
"Bash(gh pr view:*)",
|
|
40
|
+
"Bash(gh pr list:*)",
|
|
39
41
|
"Bash(gh pr diff:*)",
|
|
40
42
|
"Bash(gh run view:*)",
|
|
41
43
|
"SlashCommand(/next-backlog-item)",
|
|
@@ -46,6 +48,8 @@
|
|
|
46
48
|
"SlashCommand(/review-comments)",
|
|
47
49
|
"SlashCommand(/transcript-format)",
|
|
48
50
|
"SlashCommand(/transcript-summarise)",
|
|
51
|
+
"SlashCommand(/journal)",
|
|
52
|
+
"SlashCommand(/standup)",
|
|
49
53
|
"Skill(next-backlog-item)",
|
|
50
54
|
"Skill(verify)",
|
|
51
55
|
"Skill(commit)",
|
|
@@ -54,6 +58,8 @@
|
|
|
54
58
|
"Skill(review-comments)",
|
|
55
59
|
"Skill(transcript-format)",
|
|
56
60
|
"Skill(transcript-summarise)",
|
|
61
|
+
"Skill(journal)",
|
|
62
|
+
"Skill(standup)",
|
|
57
63
|
"WebFetch(domain:staffordwilliams.com)"
|
|
58
64
|
],
|
|
59
65
|
"deny": ["Bash(git commit:*)", "Bash(npm run:*)", "Bash(npx assist:*)"]
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@staff0rd/assist",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.64.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -112,7 +112,10 @@ var assistConfigSchema = z.strictObject({
|
|
|
112
112
|
}).optional(),
|
|
113
113
|
roam: z.strictObject({
|
|
114
114
|
clientId: z.string(),
|
|
115
|
-
clientSecret: z.string()
|
|
115
|
+
clientSecret: z.string(),
|
|
116
|
+
accessToken: z.string().optional(),
|
|
117
|
+
refreshToken: z.string().optional(),
|
|
118
|
+
tokenExpiresAt: z.number().optional()
|
|
116
119
|
}).optional(),
|
|
117
120
|
run: z.array(runConfigSchema).optional(),
|
|
118
121
|
transcript: transcriptConfigSchema.optional()
|
|
@@ -4111,9 +4114,9 @@ function addChildMoves(moves, children, newDir, parentBase) {
|
|
|
4111
4114
|
for (const child of children)
|
|
4112
4115
|
moves.push(childMoveData(child, newDir, parentBase));
|
|
4113
4116
|
}
|
|
4114
|
-
function checkDirConflict(result,
|
|
4117
|
+
function checkDirConflict(result, label2, dir) {
|
|
4115
4118
|
if (!fs20.existsSync(dir)) return false;
|
|
4116
|
-
result.warnings.push(`Skipping ${
|
|
4119
|
+
result.warnings.push(`Skipping ${label2}: directory ${dir} already exists`);
|
|
4117
4120
|
return true;
|
|
4118
4121
|
}
|
|
4119
4122
|
function getBaseName(filePath) {
|
|
@@ -4266,8 +4269,8 @@ function askQuestion(rl, question) {
|
|
|
4266
4269
|
}
|
|
4267
4270
|
|
|
4268
4271
|
// src/commands/transcript/configure.ts
|
|
4269
|
-
function buildPrompt(
|
|
4270
|
-
return current ? `${
|
|
4272
|
+
function buildPrompt(label2, current) {
|
|
4273
|
+
return current ? `${label2} [${current}]: ` : `${label2}: `;
|
|
4271
4274
|
}
|
|
4272
4275
|
function printExisting(existing) {
|
|
4273
4276
|
console.log("Current configuration:");
|
|
@@ -4618,10 +4621,10 @@ function logSkipped(relativeDir, mdFile) {
|
|
|
4618
4621
|
console.log(`Skipping (already exists): ${join18(relativeDir, mdFile)}`);
|
|
4619
4622
|
return "skipped";
|
|
4620
4623
|
}
|
|
4621
|
-
function ensureDirectory(dir,
|
|
4624
|
+
function ensureDirectory(dir, label2) {
|
|
4622
4625
|
if (!existsSync19(dir)) {
|
|
4623
4626
|
mkdirSync5(dir, { recursive: true });
|
|
4624
|
-
console.log(`Created ${
|
|
4627
|
+
console.log(`Created ${label2}: ${dir}`);
|
|
4625
4628
|
}
|
|
4626
4629
|
}
|
|
4627
4630
|
function processCues(content) {
|
|
@@ -4860,28 +4863,203 @@ function registerVerify(program2) {
|
|
|
4860
4863
|
}
|
|
4861
4864
|
|
|
4862
4865
|
// src/commands/roam/auth.ts
|
|
4866
|
+
import { randomBytes } from "crypto";
|
|
4863
4867
|
import chalk49 from "chalk";
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4868
|
+
|
|
4869
|
+
// src/lib/openBrowser.ts
|
|
4870
|
+
import { execSync as execSync23 } from "child_process";
|
|
4871
|
+
function tryExec(commands) {
|
|
4872
|
+
for (const cmd of commands) {
|
|
4873
|
+
try {
|
|
4874
|
+
execSync23(cmd);
|
|
4875
|
+
return true;
|
|
4876
|
+
} catch {
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
return false;
|
|
4880
|
+
}
|
|
4881
|
+
function openBrowser(url) {
|
|
4882
|
+
const platform = detectPlatform();
|
|
4883
|
+
const quoted = JSON.stringify(url);
|
|
4884
|
+
const commands = [];
|
|
4885
|
+
switch (platform) {
|
|
4886
|
+
case "macos":
|
|
4887
|
+
commands.push(
|
|
4888
|
+
`open -a "Google Chrome" ${quoted}`,
|
|
4889
|
+
`open -a "Microsoft Edge" ${quoted}`,
|
|
4890
|
+
`open -a "Safari" ${quoted}`
|
|
4891
|
+
);
|
|
4892
|
+
break;
|
|
4893
|
+
case "linux":
|
|
4894
|
+
commands.push(
|
|
4895
|
+
`google-chrome ${quoted}`,
|
|
4896
|
+
`chromium-browser ${quoted}`,
|
|
4897
|
+
`microsoft-edge ${quoted}`
|
|
4898
|
+
);
|
|
4899
|
+
break;
|
|
4900
|
+
case "windows":
|
|
4901
|
+
commands.push(`start chrome ${quoted}`, `start msedge ${quoted}`);
|
|
4902
|
+
break;
|
|
4903
|
+
case "wsl":
|
|
4904
|
+
commands.push(`wslview ${quoted}`);
|
|
4905
|
+
break;
|
|
4906
|
+
}
|
|
4907
|
+
if (!tryExec(commands)) {
|
|
4908
|
+
console.log(`Open this URL in Chrome, Edge, or Safari:
|
|
4909
|
+
${url}`);
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
|
|
4913
|
+
// src/commands/roam/waitForCallback.ts
|
|
4914
|
+
import { createServer } from "http";
|
|
4915
|
+
function respondHtml(res, status, title) {
|
|
4916
|
+
res.writeHead(status, { "Content-Type": "text/html" });
|
|
4917
|
+
res.end(
|
|
4918
|
+
`<html><body><h1>${title}</h1><p>You can close this tab.</p></body></html>`
|
|
4919
|
+
);
|
|
4920
|
+
}
|
|
4921
|
+
function extractCode(url, expectedState) {
|
|
4922
|
+
const error = url.searchParams.get("error");
|
|
4923
|
+
if (error) throw new Error(`Authorization denied: ${error}`);
|
|
4924
|
+
const state = url.searchParams.get("state");
|
|
4925
|
+
if (state !== expectedState)
|
|
4926
|
+
throw new Error("State mismatch \u2014 possible CSRF attack");
|
|
4927
|
+
const code = url.searchParams.get("code");
|
|
4928
|
+
if (!code) throw new Error("No authorization code received");
|
|
4929
|
+
return code;
|
|
4930
|
+
}
|
|
4931
|
+
function waitForCallback(port, expectedState) {
|
|
4932
|
+
return new Promise((resolve3, reject) => {
|
|
4933
|
+
const timeout = setTimeout(() => {
|
|
4934
|
+
server.close();
|
|
4935
|
+
reject(new Error("Authorization timed out after 120 seconds"));
|
|
4936
|
+
}, 12e4);
|
|
4937
|
+
const server = createServer((req, res) => {
|
|
4938
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
4939
|
+
if (url.pathname !== "/callback") {
|
|
4940
|
+
res.writeHead(404);
|
|
4941
|
+
res.end();
|
|
4942
|
+
return;
|
|
4943
|
+
}
|
|
4944
|
+
clearTimeout(timeout);
|
|
4945
|
+
try {
|
|
4946
|
+
const code = extractCode(url, expectedState);
|
|
4947
|
+
respondHtml(res, 200, "Authorization successful!");
|
|
4948
|
+
server.close();
|
|
4949
|
+
resolve3(code);
|
|
4950
|
+
} catch (err) {
|
|
4951
|
+
respondHtml(res, 400, err.message);
|
|
4952
|
+
server.close();
|
|
4953
|
+
reject(err);
|
|
4954
|
+
}
|
|
4955
|
+
});
|
|
4956
|
+
server.listen(port);
|
|
4957
|
+
});
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
// src/commands/roam/authorizeInBrowser.ts
|
|
4961
|
+
var PORT = 14523;
|
|
4962
|
+
var REDIRECT_URI = `http://localhost:${PORT}/callback`;
|
|
4963
|
+
var SCOPES = "user:read meetings:read transcript:read user:read.email";
|
|
4964
|
+
function buildAuthorizeUrl(clientId, state) {
|
|
4965
|
+
const params = new URLSearchParams({
|
|
4966
|
+
client_id: clientId,
|
|
4967
|
+
redirect_uri: REDIRECT_URI,
|
|
4968
|
+
response_type: "code",
|
|
4969
|
+
scope: SCOPES,
|
|
4970
|
+
state
|
|
4971
|
+
});
|
|
4972
|
+
return `https://ro.am/oauth/authorize?${params}`;
|
|
4973
|
+
}
|
|
4974
|
+
async function authorizeInBrowser(clientId, state) {
|
|
4975
|
+
openBrowser(buildAuthorizeUrl(clientId, state));
|
|
4976
|
+
const code = await waitForCallback(PORT, state);
|
|
4977
|
+
return { code, redirectUri: REDIRECT_URI };
|
|
4978
|
+
}
|
|
4979
|
+
|
|
4980
|
+
// src/commands/roam/exchangeToken.ts
|
|
4981
|
+
async function exchangeToken(params) {
|
|
4982
|
+
const body = new URLSearchParams({
|
|
4983
|
+
grant_type: "authorization_code",
|
|
4984
|
+
code: params.code,
|
|
4985
|
+
client_id: params.clientId,
|
|
4986
|
+
client_secret: params.clientSecret,
|
|
4987
|
+
redirect_uri: params.redirectUri
|
|
4988
|
+
});
|
|
4989
|
+
const response = await fetch("https://ro.am/oauth/token", {
|
|
4990
|
+
method: "POST",
|
|
4991
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4992
|
+
body: body.toString()
|
|
4871
4993
|
});
|
|
4872
|
-
|
|
4994
|
+
if (!response.ok) {
|
|
4995
|
+
const text = await response.text();
|
|
4996
|
+
throw new Error(`Token exchange failed (${response.status}): ${text}`);
|
|
4997
|
+
}
|
|
4998
|
+
return response.json();
|
|
4999
|
+
}
|
|
5000
|
+
|
|
5001
|
+
// src/commands/roam/promptCredentials.ts
|
|
5002
|
+
import enquirer6 from "enquirer";
|
|
5003
|
+
function censor(value) {
|
|
5004
|
+
const visible = value.slice(-4);
|
|
5005
|
+
return `${"*".repeat(value.length - 4)}${visible}`;
|
|
5006
|
+
}
|
|
5007
|
+
function label(name, existing) {
|
|
5008
|
+
return existing ? `${name} (${censor(existing)})` : name;
|
|
5009
|
+
}
|
|
5010
|
+
async function promptField(name, existing) {
|
|
5011
|
+
const { value } = await enquirer6.prompt({
|
|
4873
5012
|
type: "input",
|
|
4874
|
-
name: "
|
|
4875
|
-
message:
|
|
4876
|
-
validate: (
|
|
5013
|
+
name: "value",
|
|
5014
|
+
message: `${label(name, existing)}:`,
|
|
5015
|
+
validate: (v) => v.trim().length > 0 || !!existing || `${name} is required`
|
|
4877
5016
|
});
|
|
5017
|
+
return value.trim() || existing || "";
|
|
5018
|
+
}
|
|
5019
|
+
async function promptCredentials(existing) {
|
|
5020
|
+
const clientId = await promptField("Client ID", existing?.clientId);
|
|
5021
|
+
const clientSecret = await promptField(
|
|
5022
|
+
"Client Secret",
|
|
5023
|
+
existing?.clientSecret
|
|
5024
|
+
);
|
|
5025
|
+
if (!clientId || !clientSecret) {
|
|
5026
|
+
throw new Error("Client ID and Client Secret are required");
|
|
5027
|
+
}
|
|
5028
|
+
return { clientId, clientSecret };
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
// src/commands/roam/auth.ts
|
|
5032
|
+
async function auth() {
|
|
4878
5033
|
const config = loadGlobalConfig();
|
|
5034
|
+
const { clientId, clientSecret } = await promptCredentials(config.roam);
|
|
5035
|
+
config.roam = { ...config.roam, clientId, clientSecret };
|
|
5036
|
+
saveGlobalConfig(config);
|
|
5037
|
+
const state = randomBytes(16).toString("hex");
|
|
5038
|
+
console.log(
|
|
5039
|
+
chalk49.yellow("\nEnsure this Redirect URI is set in your Roam OAuth app:")
|
|
5040
|
+
);
|
|
5041
|
+
console.log(chalk49.white("http://localhost:14523/callback\n"));
|
|
5042
|
+
console.log(chalk49.blue("Opening browser for authorization..."));
|
|
5043
|
+
console.log(chalk49.dim("Waiting for authorization callback..."));
|
|
5044
|
+
const { code, redirectUri } = await authorizeInBrowser(clientId, state);
|
|
5045
|
+
console.log(chalk49.dim("Exchanging code for tokens..."));
|
|
5046
|
+
const tokens = await exchangeToken({
|
|
5047
|
+
code,
|
|
5048
|
+
clientId,
|
|
5049
|
+
clientSecret,
|
|
5050
|
+
redirectUri
|
|
5051
|
+
});
|
|
4879
5052
|
config.roam = {
|
|
4880
|
-
clientId
|
|
4881
|
-
clientSecret
|
|
5053
|
+
clientId,
|
|
5054
|
+
clientSecret,
|
|
5055
|
+
accessToken: tokens.access_token,
|
|
5056
|
+
refreshToken: tokens.refresh_token,
|
|
5057
|
+
tokenExpiresAt: Date.now() + tokens.expires_in * 1e3
|
|
4882
5058
|
};
|
|
4883
5059
|
saveGlobalConfig(config);
|
|
4884
|
-
console.log(
|
|
5060
|
+
console.log(
|
|
5061
|
+
chalk49.green("Roam credentials and tokens saved to ~/.assist.yml")
|
|
5062
|
+
);
|
|
4885
5063
|
}
|
|
4886
5064
|
|
|
4887
5065
|
// src/commands/roam/registerRoam.ts
|
|
@@ -5109,7 +5287,7 @@ function syncCommands(claudeDir, targetBase) {
|
|
|
5109
5287
|
}
|
|
5110
5288
|
|
|
5111
5289
|
// src/commands/update.ts
|
|
5112
|
-
import { execSync as
|
|
5290
|
+
import { execSync as execSync24 } from "child_process";
|
|
5113
5291
|
import * as path30 from "path";
|
|
5114
5292
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5115
5293
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
@@ -5119,7 +5297,7 @@ function getInstallDir() {
|
|
|
5119
5297
|
}
|
|
5120
5298
|
function isGitRepo(dir) {
|
|
5121
5299
|
try {
|
|
5122
|
-
|
|
5300
|
+
execSync24("git rev-parse --is-inside-work-tree", {
|
|
5123
5301
|
cwd: dir,
|
|
5124
5302
|
stdio: "pipe"
|
|
5125
5303
|
});
|
|
@@ -5130,7 +5308,7 @@ function isGitRepo(dir) {
|
|
|
5130
5308
|
}
|
|
5131
5309
|
function isGlobalNpmInstall(dir) {
|
|
5132
5310
|
try {
|
|
5133
|
-
const globalPrefix =
|
|
5311
|
+
const globalPrefix = execSync24("npm prefix -g", { stdio: "pipe" }).toString().trim();
|
|
5134
5312
|
return dir.startsWith(globalPrefix);
|
|
5135
5313
|
} catch {
|
|
5136
5314
|
return false;
|
|
@@ -5141,16 +5319,16 @@ async function update() {
|
|
|
5141
5319
|
console.log(`Assist is installed at: ${installDir}`);
|
|
5142
5320
|
if (isGitRepo(installDir)) {
|
|
5143
5321
|
console.log("Detected git repo installation, pulling latest...");
|
|
5144
|
-
|
|
5322
|
+
execSync24("git pull", { cwd: installDir, stdio: "inherit" });
|
|
5145
5323
|
console.log("Building...");
|
|
5146
|
-
|
|
5324
|
+
execSync24("npm run build", { cwd: installDir, stdio: "inherit" });
|
|
5147
5325
|
console.log("Syncing commands...");
|
|
5148
|
-
|
|
5326
|
+
execSync24("assist sync", { stdio: "inherit" });
|
|
5149
5327
|
} else if (isGlobalNpmInstall(installDir)) {
|
|
5150
5328
|
console.log("Detected global npm installation, updating...");
|
|
5151
|
-
|
|
5329
|
+
execSync24("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
|
|
5152
5330
|
console.log("Syncing commands...");
|
|
5153
|
-
|
|
5331
|
+
execSync24("assist sync", { stdio: "inherit" });
|
|
5154
5332
|
} else {
|
|
5155
5333
|
console.error(
|
|
5156
5334
|
"Could not determine installation method. Expected a git repo or global npm install."
|